perm filename NETGRF.FAI[S,NET] blob sn#679002 filedate 1982-09-23 generic text, type C, neo UTF8
COMMENT ⊗   VALID 00080 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00008 00002		TITLE NETGRF
C00013 00003	Macros
C00016 00004	Opcode Definitions
C00018 00005	Data Structures:
C00019 00006	START  - Initialization and Main Loop
C00026 00007	EXIT  - Change name and stop
C00027 00008	RDMAIL - Read the Mail
C00029 00009	       - Letter format
C00031 00010	NEWICP - Start up new ICP
C00035 00011	NEWCON - Make new connection
C00041 00012	CONEST - Connection Established.
C00047 00013	GRFLIS - Set up to listen from graphics connection
C00051 00014	GRFEST - Graphics Connection Established!
C00053 00015	SNDGRE - Send Graphics Greeting
C00055 00016	SNDIII - Send III buffer
C00058 00017	USECHK - Check to see if anyone's here
C00060 00018	STATUS - Print status of NETGRF
C00066 00019	CHGJOB - Change job paramenters
C00068 00020	OUTWHR - Write out WHERE table for MAIL, FINGER, etc.
C00069 00021	-------------------------------------------------
C00070 00022	INTSER - Interrupt Service Routine
C00081 00023	CLKSER - Service clock interrupt
C00084 00024	IMPCHG - IMP status change
C00088 00025	PTYSER - Service PTY
C00092 00026	IMLHAK - Hack Imlac compatability mode
C00099 00027	NEWPRO - Interpet new TELNET protocol
C00110 00028	NETOPN - Open duplex network connection
C00115 00029	SPY    - Find out who is using NETGRF
C00117 00030	GRISER - Graphic Input Service
C00118 00031	RDINQRS- Read Inquiry Response
C00122 00032	RCVCNT - Recieve Graphics Count
C00123 00033	RCV32  - Receive 32 bits
C00124 00034	GRFERM - Graphics error message
C00125 00035	IIISIM - Simulate III Display
C00139 00036	SNDCOOR- Send coordinates
C00141 00037	SNDCNT - Send count (for NGP)
C00142 00038	STRLEN - Length of ASCIZ string
C00143 00039	SNDSTR - Send string (for NGP)
C00144 00040	SNDNAM - Send segment name (for NGP)
C00145 00041	-------------------------------------------------
C00146 00042	GET1K  - Get a 1024 word block
C00148 00043	REL1K  - Release a 1024 word block
C00150 00044	MKPBLK - Make a Process Block
C00153 00045	MKPROC - Make a Process
C00155 00046	KLPROC - Kill a Process or Process Block
C00157 00047	ENQUE  - Enter into Queue
C00160 00048	ENTCLK - Enter into Clock Queue
C00165 00049	DEQUE  - Delete first entry from queue
C00167 00050	SRHQUE - Search queue and delete entry
C00170 00051	SCHED  - Schedule a Process
C00171 00052	RESCHED- Request to be Rescheduled (also WSCHED)
C00174 00053	DELAY  - Schedule a Process in future
C00175 00054	POSTPON- Request to be Rescheduled
C00177 00055	RUNPROC- Run a Process
C00179 00056	PREXIT - Process Exit
C00180 00057	GETPRO - Get process pointer from stack pointer
C00181 00058	GTUSID - Get User Id
C00183 00059	KLUSER - Kill user and release associated storage	***
C00190 00060	FNDPTY - Find user number from pseudoteletype line
C00191 00061	MKRBUF - Make Ring Buffers for System I/O
C00193 00062	KLRBUF - Kill Ring Buffers
C00194 00063	ENLOCK - Enter interlock
C00196 00064	DELOCK - Leave interlock
C00198 00065	LOKENB - Enable interrupts inside an interlock
C00199 00066	LOKWAI - Wait for interlock
C00200 00067	USERMO - Enter user mode				***
C00202 00068	LOGIT  - Log messages
C00206 00069	SETCHK - Initialize checksum of pure code
C00209 00070	SYSCHK - Checksum pure code and fix if pure code modified
C00213 00071	CALCHK - Calculate checksum
C00214 00072	-------------------------------------------------
C00215 00073	IMPOCNT- Return number of bytes which we can send IMP without waiting
C00217 00074	IMPOCHR- Send character to IMP
C00222 00075	IMPOUT - Output buffer to IMP
C00224 00076	IMPSTR - Output string to IMP
C00226 00077	IMICHS - Skip if character ready from IMP
C00234 00078	IMPIN  - Input buffer from IMP
C00235 00079	Misc. output routines:  TYPOCT,TYPDEC,DRYROT
C00237 00080	Storage
C00249 ENDMK
C⊗;
	TITLE NETGRF
;
;  Network Graphics Protocol Server
;	Also handles multiple TELNET users
;	Also accepts D. King (of UCB) variant of NGP
;		(4 bits per byte down TELNET connection)
;
IFNDEF NUSERS,<	PRINTS/NUSERS(3),NEWPRO(1),OLDPRO(0),GRFPRO(1)
/↔		.INSERT TTY:
>
.INSERT INTDEF.FAI[1,TVR]
;;;.INSERT IMPDEF.FAI[NET,TVR]
.INSERT NETDEF.[S,SYS]
.LIBRARY TVRLIB.REL[SUB,SYS]

EXTERNAL JOBAPR,JOBCNI,JOBTPC

; Parameters
IFNDEF NUSERS,< ↓NUSERS ←← 3 >	;Maximum number of users
IFNDEF NEWPRO,< ↓NEWPRO ←← 1 >	;Assume new protocol
IFNDEF OLDPRO,< ↓OLDPRO ←← 0 >	;Assume ignoring old protocol
IFNDEF GRFPRO,< ↓GRFPRO ←← 1 >	;Assume graphics
IFNDEF DEBPRC,< ↓DEBPRC ←← 0 >	;Assume not debugging process code
IFNDEF DKPRO ,< ↓DKPRO  ←← GRFPRO >;Assume D. King format allowed
IFNDEF CHKSW ,< ↓CHKSW  ←← 0 >	;Assume not wanting checksum feature
IFNDEF SPYSW ,< ↓SPYSW  ←← 1 >	;Assume wanting to know who's using it
IFNDEF NEWSW ,< ↓NEWSW  ←← 0 >	;For testing
IFNDEF IMLSW ,< ↓IMLSW  ←← 0 >	;Handle Extended-ASCII in IMLAC mode

IFG NUSERS-7,<	.FATAL Number of users limited to 7 because of I/O channels limitations.
>;IFG

; GRFMUL is multiplier for tables which need to be expanded for graphics
; due to second IMP connection.
IFE GRFPRO,<	↓GRFMUL ←← 1 >
IFN GRFPRO,<	↓GRFMUL ←← 2 >

↓NSPECU ←← 1		;Number of Special uses
↓U.ICP ←← GRFMUL*NUSERS	;ICP user

↓QUESIZ	←←  3		;Size of queue block
↓PROCSZ	←←  100		;Size of process block
↓LOCKSZ	←←  1		;Size of lock block
↓MTSIZE ←← 11		;Largest is GET ALLOCATIONS
↓DAYTIC ←← =24*=60*=60*=60	;Number of tics per day

;Checksum channel (not used normally and IOPUSHed and IOPOP when it is)
IFN CHKSW,<
↓CHKCHN	←← 0		;Channel used for checking
>;IFN CHKSW
; Accumulator definitions
↓RET 	←  1		;Results returned here
↓A	←  2
↓B	←  A+1
↓C	←  B+1
↓D	←  C+1
↓E	←  D+1
↓F	←  E+1
↓TAC	← 11		;Temperary ac
↓TAC2	← TAC+1		;Another temperary
↓U	← 16		;User number
↓P	← 17		;Stack pointer

; Flag bits
↓WHOBIT	← 1B0		;WHO line in progress
↓GREBIT	← 1B1		;Has recieved greeting from graphics connection
↓INQBIT	← 1B2		;We have inquired him/her

; Random TTY bit definitions
FULTWX←←4		; ON FOR HALF DUPLEX
xon←←2			; don't generate lf after CR

COMMENT ⊗ Problems:

Calls to DRYROT must be fixed someday
OPEN in NEWCON is not reentrant!!!
Allocation of ring buffer is such that storage allocated them can be lost
   under certain obscure conditions.

⊗;
COMMENT $ Debugging character
< Enter interlock
> Leave interlock
≤ Enter clock queue
⊗ CLKSER running
≥ Leave clock queue
+ Schedule process
- Run process
* IMP status change
{ IMP input noticed
} IMP input finished
← PTY output noticed
→ PTY output finished
$;
SUBTTL Macros
	DEFINE MOVBI(AC,BITS)
<IFE BITS∧777777,< MOVSI AC,(BITS);>IFE BITS∧777777000000,<MOVEI AC,BITS;>MOVE AC,[BITS]>
IFN NEWPRO,<
	DEFINE PROBIT `(X) <1B`X>
>

	DEFINE EXIT(AC)
<IFIDN <AC><><	PUSHJ P,DOEXIT	>IFDIF <AC><>,<	CALLI AC,12>>

	DEFINE ENTERLOCK(LOCKBLOCK)
<	PUSHJ P,ENLOCK
	LOCKBLOCK
>
	DEFINE LEAVELOCK(LOCKBLOCK)
<	PUSHJ P,DELOCK
	LOCKBLOCK
>
	DEFINE TURNON(ADR)
<	SKIPL LOKCNT
	PUSHJ P,LOKENB
	IMSKST ADR
>
	DEFINE TURNOFF(ADR)
<	IMSKCL ADR
>
	DEFINE PUSHACS
<	PUSHJ P,PUSHIT↑
>
	DEFINE POPACS
<	PUSHJ P,POPIT↑
>
	DEFINE ACCUMULATORS(LIST)
<	ACPTR←←2
	FOR AC⊂(LIST)
<	AC←ACPTR
	ACPTR←←ACPTR+1
>>

;FATAL ERROR MESSAGE.

IFNDEF FATAL.
<	DEFINE FATAL(STR){PUSHJ 17,FATAL.↑↔JFCL [ASCIZ/STR/]}
>
IFNDEF WARN.
<	DEFINE WARN(STR){PUSHJ 17,WARN.↑↔JFCL [ASCIZ/STR/]}
>

;CHAIN TOGETHER BIT TABLES FOR RAID
	DEFINE BITDEFS(BITS)
<IFNDEF .BTLNK, < .BTLNK←←0 
>;	.BTLNK
	.BTLNK←←.BTLNK*1000000+.
	.BTABL←←$.
	FOR BIT⊂(BITS)
<IFIDN <><BIT>< 0
;>	RADIX50 0,BIT
>	BLOCK =36+.BTABL-$.
>
BITDEF<INTSWW,INTSWD,INTSHW,INTSHD,INTTTY,INTPTI,INTMAIL,INTWAIT,INTPTO,INTPAR,INTCLK
,INTINR,INTINS,INTIMS,INTINP,INTTTI,INTQXF,,,POV,,,ILM,NXM,,,OLDCLK,,,INTFOV,,,INTOV,,,>
;BITDEF<WHOBIT,GREBIT,INQBIT>


IFN NEWSW,<
	DEFINE ENTERLOCK(LOCKBLOCK)
<
	AOSE LOCKBLOCK
	PUSHJ P,ENLWAI
>
	DEFINE LEAVELOCK(LOCKBLOCK)
<	SOSL LOCKBLOCK
	PUSHJ P,ENLCON
>
	DEFINE TURNON(ADR)
<	SKIPL LOKCNT
	PUSHJ P,LOKENB
	IMSKST ADR
>
>;IFN NEWSW

;GENERATE SUBROUTINE CALL
	DEFINE CALL(NAME,X1,X2,X3,X4,X5,X6){
	XLIST
IFDIF <><X1>{PUSH 17,X1
 IFDIF <><X2>{PUSH 17,X2
  IFDIF <><X3>{PUSH 17,X3
   IFDIF <><X4>{PUSH 17,X4
    IFDIF <><X5>{PUSH 17,X5
     IFDIF <><X6>{PUSH 17,X6
}}}}}}
IFDIF <><NAME>{PUSHJ P,NAME
}
	LIST}
SUBTTL Opcode Definitions

;TELNET opcodes
DEFINE TELOPC(SHORT,CODE,DESCR)
<	↓SHORT←←CODE		;DESCR
>
DEFINE TELOPT(SHORT,CODE,DESCR)
<	↓SHORT←←CODE		;DESCR
>

;Graphics opcodes
DEFINE NGPOP(SHORT,CODE,OPT,LONG)
<	↓SHORT←←=CODE		;OPT LONG
>
DEFINE NGPDEF(SHORT,CODE,LONG)
<	↓SHORT←←=CODE		;LONG
>
DEFINE NGPINQ(SHORT,CODE,OPT,LONG)
<	↓SHORT←←=CODE		;OPT LONG
>

IFN NEWPRO,<	.INSERT TELOP.DEF[CSP,SYS]	>
IFN GRFPRO,<	.INSERT NGPOP.DEF[CSP,SYS]	>

IFN DKPRO,<
; Losing D. King format:
;	<begin graphics>
;;block of form:
;	<high order 4 bits>
;	<low order 4 bits>
;	<end graphics>
DKESC←←20
DKBEG←←21
DKEND←←22
>;IFN DKPRO
SUBTTL Data Structures:

COMMENT ⊗

PROCESS BLOCK:

Process pointer addresses last word in block

STACK:	< n words of stack>
	IOWD -n,STACK
	XWD <queue datum>,<user number or -1>
	XWD <next in queue>,<address of saved acs>
⊗;
↓%LINK	←← 0
↓%PACS	←← 0
↓%USER	←← -1
↓%DATUM	←← -1
↓%PDLIO	←← -2
↓%PDLSZ ←← PROCSZ-3

COMMENT ⊗

QUEUE BLOCK:

	<first element of queue>
	<last element of queue>
	<number of entries in queue>

Queue elements are linked thru the left half of zeroth word of each element

⊗;
SUBTTL START  - Initialization and Main Loop

	LOC 124
	REEADR
	RELOC
	
$BGNET↑:
START:
REEADR:	MOVE TAC,[SIXBIT/NETGRF/]
	SETNAM TAC,
	RESET
	MOVE P,PDLIOWD			;Set up PDL
	SKIPE DEBUG			;Don't RESET if detached (lose letter)
	  RESET				;Flush old I/O
	SETZM BEGZER			;Zero storage for repeatability
	MOVE TAC,[XWD BEGZER,BEGZER+1]
	BLT TAC,ENDZER
	SETOM LOKCNT			;Initialize lock count
	MOVEI TAC,51			;Default socket
	MOVEM TAC,LSOCKT+U.ICP
	PJOB TAC,			;Remember our job number
	MOVEM TAC,THISJOB
;	HRLZM TAC,NXTSOC		;Next socket to use for ICP
	MOVEI TAC,INTSER		;Set up interrupt vector
	MOVEM TAC,JOBAPR↑
	HLRO TAC,JOBSYM↑		;Save symbols if DDT is loaded
	SUB TAC,JOBSYM
	MOVN TAC,TAC
;;;	SKIPN JOBDDT
;;;	HLRZ TAC,JOBSA↑			;Core down
	MOVEM TAC,OLDFF
;;;	CORE TAC,
;;;	  JFCL
	SETOM JOBFF↑			;To catch system, trying to make buffers.
	SETZ TAC,
	GETNAM TAC,			;Same name as segment?
	SEGNAM TAC2,
	CAMN TAC,TAC2
	JRST [	SETZ TAC,		;Yes, flush spurious segment
		CORE2 TAC,
		JFCL
		JRST .+1]
	MOVEI TAC,=5			;Number of times DRYROT may be called
	MOVEM TAC,LOSCNT		;before giving up the ghost
	MOVSI TAC,400000		;Init. user table
	ASH TAC,1-NUSERS
	MOVEM TAC,USEMAP
	MOVSI TAC,'IMP'			;Get IMP's buffer size
	BUFLEN TAC,
	CAILE TAC,PROCSZ-2		;Bigger than a process block?
	MOVEI TAC,PROCSZ-2		;Yes, use latter size
	MOVEM TAC,IMPSIZ
	MOVSI TAC,'TTY'			;Get TTY's buffer size
	BUFLEN TAC,
	CAILE TAC,PROCSZ-2		;Bigger than a process block?
	  PUSHJ P,DRYROT		;HELP!!!! (SOMETHING BETTER HAS TO BE DONE HERE)
	MOVEM TAC,TTYSIZ
IFN CHKSW,<
	PUSHJ P,SETCHK			;Set checksum
>;IFN CHKSW
	SETOM DEBUG			;Set debug flag if not detached
	GETLIN DEBUG
	AOSN DEBUG
	  JRST[	PUSH P,['   TVR']
		PUSH P,[[ASCIZ/;; NETGRF started.
/]]↔		PUSHJ P,BLAST↑
		JRST .+2]
	OUTSTR[ASCIZ/
SU-AI Network Graphics Server
/]
;Lastly, enable interrupt (will probably get one immediately).
	INTMSK [¬(INTPTI!INTPTO)]
	MOVE TAC,[IFN CHKSW,<INTPAR!>INTMAIL!INTTTI!INTIMS!INTPTO!INTPTI!INTINP]
	INTENB TAC,
;	IWKMSK [¬(INTTTI)]
	SETO U,				;System task
	PUSH P,[USECHK]
	PUSH P,[5*=60*=60]		;Check back in five minutes to see if anyone's
	PUSHJ P,DELAY			;home
IFN DEBPRC,<
	skipn debug
	jrst skptst
	PUSH P,[TEST2]
	PUSH P,[2*=60]			;Check back in five minutes to see if anyone's
	PUSHJ P,DELAY			;home
	PUSH P,[TEST3]
	PUSH P,[5*=60]			;Check back in five minutes to see if anyone's
	PUSHJ P,DELAY			;home
	PUSH P,[TEST1]
	PUSH P,[1*=60]			;Check back in five minutes to see if anyone's
	PUSHJ P,DELAY			;home
	PUSH P,[TEST4]
	PUSH P,[10*=60]			;Check back in five minutes to see if anyone's
	PUSHJ P,DELAY			;home
skptst:
>;IFN DEBPRC
	MOVEI U,U.ICP			;Set User number for NEWICP
	PUSH P,[NEWICP]			;Start up ICP listen
	PUSHJ P,SCHED
	SETO U,				;System.
	SETO TAC,			;Are we detached?
	GETLIN TAC
	AOJN TAC,MAIN			;No
REPEAT 0,<
;Set up to send letter to logger.
	SETZ TAC,
	GETNAM TAC,			;Tell the logger our name
	MOVEM TAC,THISNAM
	MOVEI TAC,2*=60			;Wait two minutes before giving up.
	MOVEI TAC2,1			;Number of seconds to sleep for mail
; Send a letter to the logger
MAILOG:	SKPSEN [SIXBIT/LOGGER/			;Yes, tell logger where we are
		THISJOB]
	JRST [	SLEEP TAC2,			;Wait for logger's mailbox to empty
		SOSG TAC,MAILOG			;But give up after a while
		EXIT ]
	SKIPA
	EXIT				;Too many or too few LOGGER's. Give up.
>;REPEAT 0
	PUSH P,['NETGRF']
	PUSH P,[[ASCIZ/;; NETGRF started.
/]]↔	PUSHJ P,BLAST↑
MAIN:	PUSH P,[PRIQUE]			;Run any processes in priority queue
	PUSHJ P,DEQUE
	JUMPN RET,MAIN2
	PUSH P,[RUNQUE]			;Then the run queue
	PUSHJ P,DEQUE
	JUMPE RET,WAITER
MAIN2:	PUSH P,RET			;Get ready to run him
	PUSHJ P,RUNPROC
	JRST MAIN			;Back for more
WAITER:	SETO TAC,			;Watch the timing race
	IMSKCR TAC
	SKIPE RUNWAI			;Anything waiting to run?
	JRST [
;		INTMSK TAC		;Yes, Restore interrupts
;		JRST MAIN ]			;and run it
		skipn runque		;Bug check
		skipe prique
		JRST [ INTMSK TAC		;and run it
		       JRST MAIN ]
		pushj p,dryrot
		setzm runwait
		jrst .+1]
	IMSTW TAC			;Re-enable and wait for interrupt to wake us
	JRST MAIN
ARRAY INTBUF[6]
SUBTTL EXIT  - Change name and stop

DOEXIT:	MOVE TAC,[SIXBIT/HLTGRF/]
	SETNAM TAC,
	PUSHJ P,LOGIT
	XWD 7,[ASCIZ/Exiting.
/]↔	0
	CALLI 12
SUBTTL RDMAIL - Read the Mail
BEGIN RDMAIL
; Start off at interrupt level in order to stop display generating
; programs fast enough so that they don't modify their display (we
; hope).
	ASCID/NTMAI/
↑RDMAIL:SRCV INLET		;Read the mail
	JRST NOMAIL
RDMAI1:	MOVE A,INLET+3		;Who does it claim to be from?
IFN GRFPRO,<
	CAMN A,[SIXBIT/UPGIOT/]	;Somebody wanting to do display output
	JRST SNDIII
>;IFN GRFPRO
	PUSHJ P,[PUSHJ P,SCHED		;The rest may as well be done at user level
		JRST INTDIS ]
	SKIPE DEBUG
	OUTSTR[ASCIZ/Reading the mail.../]
	CAMN A,[SIXBIT/DEBUG?/]	;LOGGER perhaps?
	JRST NEWCON		;Yes, open new connection
	CAMN A,[SIXBIT/DETJOB/]	;Unhang a PTY?
	JRST CHGJOB		;  Yes, go do it!
	CAMN A,[SIXBIT/D.KING/]	;Dave King format?
	JRST CHGJOB		;  Yes, go do it!
	CAMN A,[SIXBIT/STATUS/]	;Status?
	JRST XSTAT
	SKIPN DEBUG
	jrst [
		pushj p,LOGIT
		XWD 7,[asciz/Garbage in mail box: /]
		XWD 6,INLET+3
		0
		jrst .+1]
	SKIPE DEBUG
	OUTSTR[ASCIZ/Garbage!
/]
	SKPME INLET
	JRST NOMAIL
	PUSH P,[RUNQUE]
	PUSHJ P,RESCHED		;Give others a chance before reading more
NOMAIL:	SKIPE DEBUG
	OUTSTR[ASCIZ/Gee, i thought i had some mail. Oh, well
/]
↑MAILOK:TURNON [INTMAIL]	;Let mail interrupts happen again
	POPJ P,
BEND RDMAIL
;       - Letter format
COMMENT ⊗

DEBUG? - Request for connection from LOGGER
	local socket #  (32 bits, right adjusted)
	foreign socket # (32 bits, right adjusted)
	host-link number (16 bits, right adjusted)
	sixbit /DEBUG?/ if telnet is being debugged
	sixbit /<name of the host>/	(left-justified)
DETJOB - Detach job from owned PTY
	Job number of requestor
	<ignored>
	<ignored>
	sixbit/DETJOB/
	PTY number to be detached from
D.KING - Enable Dave King format
	Job number of requestor
	<ignored>
	<ignored>
	sixbit/D.KING/
	PTY number to be enabled
	Interrupt bit to send on completion	;5
STATUS - Send status information to arbitrary teletype
	Job number of requestor
	<ignored>
	<ignored>
	sixbit/STATUS/
	TTY number to be typed upon
	Interrupt bit to send on completion	;5
UPGIOT - Handle display instruction
	Job number of caller			;0
	Address of display buffer		;1
	POG number				;2
	SIXBIT/UPGIOT/				;3
	PTY number				;4
	Interrupt bit to send on completion	;5
⊗;
SUBTTL NEWICP - Start up new ICP
BEGIN NEWICP

;Initial connection protocol:
;;The following is done by the LOGGER
;	listen(local = L, size = 32)
;	[wait for connection]
;	send(socket = L, data = S)
;	close(socket = L)
;;Server program
;	init(local = S, foreign = U+3, size = Bu)	;The foreign site specifies Bu
;	init(local = S+1, foreign = U+2, size = Bs)	;We specify Bs
;	
;init(local = S, foreign = U+3, size = Bu)	;The foreign site specifies Bu
;
↑NEWICP:
	caie u,u.icp		;Check user number
	  pushj p,dryrot	;  LOSE!
	INIT U.ICP,17		;Use dump mode for ICP listen
	    SIXBIT/IMP/
	    0
	  JRST NOIMP		;Lose!
	MOVSI TAC,400000⊗(-U.ICP)	;Mark IMP connection as in use
	ORM TAC,IMPMAP
	PUSH P,LSOCKT+U.ICP	;Contact socket
	PUSH P,[0]		;Doing LISTEN!
	PUSH P,[0]
	PUSH P,[=32]		;Bytesize
	PUSHJ P,NETOPN
;	MOVEI 3,=8		;Get next socket to use
;	ADD 3,NXTSOC		;After incrementing for next guy
;	EXCH 3,NXTSOC
	MOVEI 2,21		;Gensym a socket
	MTAPE U.ICP,2
	PUSH P,3		;Save socket on stack
	LSH 3,4
	OUTPUT U.ICP,[IOWD 1,3	;Send new socket number
		      0]
	MOVEI 1,7		;Put status into ACs
	MOVE 3,LSOCKT+U.ICP
	MTAPE U.ICP,1
;Fake letter from LOGGER as kludge
;;	local socket #  (32 bits, right adjusted)
;;	foreign socket # (32 bits, right adjusted)
;;	host-link number (16 bits, right adjusted)
;;	sixbit /DEBUG?/ if telnet is being debugged
;;	sixbit /<name of the host>/	(left-justified)
	TURNOFF [INTMAIL]	;Disable mail interrupts for fake letter
	MOVEM 7,INLET+2
	POP P,INLET		;Local socket number
	MOVEM 6,INLET+1		;Set foreign socket number
;*** Kludge: Just host number for now ****
	MOVE 1,[SIXBIT/@@@@@@/]
	MOVEM 1,INLET+4
	PUSH P,[POINT 6,INLET+4]	;Make byte pointer on stack
	MOVSI 3,(<IDPB 1,>)
	HRRI 3,(P)
	MOVEI 1,"#"
	XCT 3
	LDB 1,[POINT 8,2,35-8]
	PUSH P,1
	PUSH P,[=10]
	PUSH P,3
	PUSHJ P,WRINT
	MOVE 1,[SIXBIT/@@@@@@/]
	XORM 1,INLET+4		;Cheap ASCII to SIXBIT!
	POP P,(P)
;*** End kludge: Just host number for now ****
	PUSH P,[NEWCON]		;Make new connection
	PUSHJ P,SCHED
	PUSH P,[CLOICP]		;Wait a bit and then close
	PUSH P,[=15]
	PUSHJ P,DELAY
	POPJ P,

;Close ICP and get ready for another connection
↑CLOICP:SETZ A,			;No message
	PUSHJ P,KLUSER		;Flush ICP connection and any stray processes
	JRST NEWICP		;Start new ICP

NOIMP:	PUSH P,[NEWICP]
	PUSH P,[3*=60]		;Wait awhile and try again
	PUSHJ P,DELAY
	POPJ P,

BEND NEWICP
SUBTTL NEWCON - Make new connection
BEGIN NEWCON
COMMENT $ Format of letter from LOGGER
;INLET:	local socket #  (32 bits, right adjusted)
;	foreign socket # (32 bits, right adjusted)
;	host-link number (16 bits, right adjusted)
;	sixbit /debug?/ if telnet is being debugged
;	sixbit /<name of the host>/	(left-justified)
$;
MLS←←0
MFS←←1
HOSLNK←←2
DEBUGQ←←3
HOSNAM←←4

↑NEWCON:PUSHJ P,GTUSID
	SKIPN DEBUG
	jrst [	pushj P,LOGIT↑
		XWD 7,[asciz/Connection requested from /]
		XWD 6,INLET+HOSNAM
		XWD 7,[ASCIZ/Assigned user #/]
		XWD 8,U
		0
		JRST NEWCO1]
	OUTSTR[ASCIZ/Request from host /]
	PUSH P,INLET+HOSNAM
	PUSHJ P,TYPSIX
	OUTSTR[ASCIZ/
Host #/]
	MOVE TAC,INLET+HOSLNK
	PUSH P,TAC
	PUSHJ P,TYPDEC
	OUTSTR[ASCIZ/, Foreign Socket #/]
	PUSH P,INLET+MFS
	PUSHJ P,TYPOCT
	OUTSTR[ASCIZ/, Local Socket #/]
	PUSH P,INLET+MLS
	PUSHJ P,TYPOCT
	OUTSTR[ASCIZ/, Server User #/]
	PUSH P,U
	PUSHJ P,TYPDEC
	OUTSTR CRLF
NEWCO1:	MOVEI A,3		;Make index into header
	IMULM U,A
	MOVSI TAC,OUTHDR(A)
	HRRI TAC,INHDR(A)
	ENTERLOCK IMMLOK
	MOVEM TAC,IMPBLK+2	;Tell system where our buffers are
	MOVEI TAC2,10
	MOVEM TAC2,IMPBLK
	MOVE TAC2,[OPEN 000,IMPBLK]	;Init IMP
	DPB U,[POINT 4,TAC2,12]
	XCT TAC2			;OPEN CHAN,IMPBLK
	JRST [	MOVEI A,[ASCIZ/Can't INIT IMP.  Assuming letter was a fake.
/]↔		LEAVELOCK IMMLOK
		PUSHJ P,MAILOK
		JRST KLUSER ]
	LEAVELOCK IMMLOK
	MOVEI TAC,=8
	DPB TAC,[POINT 6,INHDR+1(A),11]	;Set byte size
	DPB TAC,[POINT 6,OUTHDR+1(A),11]
	MOVEM TAC,BYTSIZ(U)
	PUSH P,[2]		;Buffer buffers for IMP
	PUSH P,IMPSIZ
	PUSHJ P,MKRBUF
	MOVEM RET,INHDR(A)	;Make header for system input
	PUSH P,[2]
	PUSH P,IMPSIZ
	PUSHJ P,MKRBUF
	MOVEM RET,OUTHDR(A)	;Make header for system output
	MOVSI TAC2,(<OUTPUT 000,>)	;Initial output to set up buffers
	DPB U,[POINT 4,TAC2,12]
	XCT TAC2		;OUTPUT CHAN,
	SOS OUTHDR+2(A)		;Fix byte count
	MOVEI TAC,3		;Save initial status
	IMULM U,TAC
	MOVEI A,2
	ADD TAC2,[<MTAPE 000,A>-<OUTPUT 000,>]
	XCT TAC2		;MTAPE CHAN,[GET_STATUS...]
	MOVEM B,IMPST1(TAC)
	MOVEM C,IMPST1+1(TAC)
	ADD TAC2,[[17		;Set timeouts
		   BYTE (6) =1,0,=30,=63,0]-A]
; Bytes mean:		 CLS,RFNM,ALLOC,RFC,INP
	XCT TAC2		;MTAPE CHAN,[SET_TIMOUTS...]
	MOVE TAC2,[GETSTS 000,IMPST1+2(TAC)]	;Get I/O status
	DPB U,[POINT 4,TAC2,12]
	XCT TAC2		;GETSTS CHAN,IMPST1I+2(A)
	MOVSI TAC,400000	;Mark IMP connection as in use
	MOVN TAC2,U
	ROT TAC,(TAC2)
	ORM TAC,IMPMAP
	SETZM PROMAP(U)
;Initial connection protocol:
;;The following is done by the LOGGER
;	listen(local = L, size = 32)
;	[wait for connection]
;	send(socket = L, data = S)
;	close(socket = L)
;;Server program
;	init(local = S, foreign = U+3, size = Bu)	;The foreign site specifies Bu
;	init(local = S+1, foreign = U+2, size = Bs)	;We specify Bs
;	
;init(local = S, foreign = U+3, size = Bu)	;The foreign site specifies Bu
	MOVE TAC2,INLET+MLS	;Set local socket
	MOVEM TAC2,LSOCKT(U)
	PUSH P,TAC2
	MOVE TAC2,INLET+HOSLNK	;Set host number
	MOVEM TAC2,HOSTNU(U)
	PUSH P,TAC2
	MOVE TAC2,INLET+MFS	;Set foreign socket
	MOVEM TAC2,FSOCKT(U)
	ADDI TAC2,3
	PUSH P,TAC2
	PUSH P,[8]		;Set byte size
	MOVE TAC2,INLET+HOSNAM	;Copy host name from letter into internal tables
	MOVEM TAC2,HOSTNA(U)
	LSH TAC2,-14
	MOVEM TAC2,WHRTAB(U)	;Set Host name part of WHERE table
IFE NUSERS-1,<			;If one user version, set name to G-XXXX
	TLO TAC2,'G- '
	SETNAM TAC2,
>
	PUSHJ P,MAILOK
	PUSH P,[OPNCHK]		;Start process to flush if no connection
	PUSH P,[1*=60*=60]	;in one minute
	PUSHJ P,DELAY
	PUSHJ P,NETOPN		;Open network connection
	JRST CONEST		;Connection established
BEND NEWCON
SUBTTL CONEST - Connection Established.
BEGIN CONEST

↑CONEST:
	SKIPN DEBUG
	JRST CONES2
	OUTSTR[ASCIZ/TELNET connection open for user #/]
	PUSH P,U
	PUSHJ P,TYPDEC
	OUTSTR CRLF
CONES2:	MOVE TAC,[MTAPE 000,[15↔3↔0↔0]]	;Give him default allocation
	DPB U,[POINT 4,TAC,12]
	XCT TAC				;MTAPE CHAN,[ALLOC↔3↔0↔0]
IFN DKPRO,<
	SETZM DKFLAG(U)			;Initialize D. King kludge
	SETOM DKIACT(U)
	SETZM DKIACT+NUSERS(U)
	SETZM DKOACT(U)
	SETZM DKIHI(U)
	SETZM DKESCF(U)
>;IFN DKPRO
	PUSH P,[[ASCIZ*SU-AI Network Graphics Server.
Please type "HELP NETGRF<return>".
*]]↔	PUSHJ P,IMPSTR
	PUSHJ P,MKPBLK			;Get another process block
	SUBI RET,PROCSZ-1		;Point to first word
	MOVEM RET,PTOBUF(U)		;Save it
	PTYGET PTYNUM(U)		;Get a PTY
	JRST [	MOVEI A,[ASCIZ/No psuedoteletypes available, sorry.
/]↔		PUSH P,A		;What a pity, no PTY
		PUSHJ P,IMPSTR
		PUSHJ P,LOGIT
		XWD 7,[ASCIZ/No PTYs available!!!/]
		0
		JRST KLUSER ]		;Flush him!
	ACCTIM TAC,			;Include date and time
	PUSH P,TAC
	PUSH P,[PUSHJ P,IMPOCHR]
	PUSHJ P,WRDAYT↑
	PUSH P,[[ASCIZ/  TTY/]]		;Tell user his/her TTY number
	PUSHJ P,IMPSTR
	PUSH P,PTYNUM(U)
	HRRZS (P)
	PUSH P,[=8]
	PUSH P,[PUSHJ P,IMPOCHR]
	PUSHJ P,WRINT↑
IFN GRFPRO,<
	PUSH P,[ [ASCIZ/	*GICP*/] ]
	PUSHJ P,IMPSTR
	MOVE TAC,LSOCKT(U)
	ADDI TAC,2
	PUSH P,TAC
	PUSH P,[=11]
	PUSH P,[PUSHJ P,IMPOCHR]
	PUSHJ P,WROCT↑
>;IFN GRFPRO
	MOVBI TAC,PROBIT(→$SGA)+PROBIT(→$ECHO)	;Tell other end we will suppress
						;go ahead and echoing
	ORM TAC,PROMAP(U)
	PUSH P,[[BYTE (8) $IAC,$WILL,$SGA,$IAC,$WILL,$ECHO,15,12,".",0]]
	PUSHJ P,IMP8STR
	SKIPE DEBUG
	  OUTSTR[ASCIZ/ [WILL 3]/]
	MOVE A,PTYNUM(U)		;Tell everyone it's a net connection
	PTGETL A
;;	TLO B,FULTWX!FCS!XON
printx PTY needs its bits setup more carefully, i think.  It's changed in N years.
	TLO B,FULTWX!XON
;;	move tac,hostnum(u)	;UCB doesn't insert LF after CR!
;;	cain tac,=98
;;	tlz b,fultwx!xon
	PTSETL A
	MOVSI TAC,034400(A)
	HRROI B,TAC
	TTYSET B,
	DPB A,[POINT 12,WHRTAB(U),11]	;Set PTY number entry in WHERE table
	PUSHJ P,OUTWHR			;Force out WHERE table for MAIL, FINGER, etc.
COMMENT ⊗ (From LISTNR[CSP,SYS])
LISTNR uses the FULTWX bit to control the state of echoing of the PTY
because the NOECHO bit in the TTY DDB is used by the program connected
to the PTY (and hence conflicts) and, anyway, it only works when the
PTY is not in monitor mode.  But the FULTWX bit doesn't quite do the
right thing altogether either.  It has the problem that line feeds
that the system inserts still get echoed.  To fix that, the XON bit
is turned on so that the system won't insert line feeds and the
LISTNR inserts its own line feeds.
⊗
	SETZM PTOFUL(U)			;Mark PTY output buffer as empty (full if
					;non-negative)
	SETZM ALLFUL			;We have an empty buffer now
	TURNON [INTINP!INTPTI!INTPTO]
IFN GRFPRO,<
	ADDI U,3		;We're setting up graphs part now
	PUSH P,[GRFLIS]		;Make process to handle graphics connection
	PUSHJ P,SCHED
	SUBI U,3		;Reset user ID
>;IFN GRFPRO
	POPJ P,			;Done, we can leave now

;Check to see if connection was successfully opened.  This really shouldn't
;be necessary as the connection should time out, but since IMPSER is not
;likely to get fixed soon, it is better to check so as not to leave
;spurious jobs and connections lying around.
↑OPNCHK:SKIPE PTYNUM(U)		;PTY assigned?
	  JRST PREXIT		;  Yes, assume opened
	PUSH P,[=60]		;Wait a second in case already flushing
	PUSHJ P,POSTPONE
	MOVEI A,[ASCIZ/Connection timed out.
/]↔	JRST KLUSER
SUBTTL GRFLIS - Set up to listen from graphics connection
;Falls thru from CONEST
IFN GRFPRO,<
;Get ready to listen for graphics connection
↑GRFLIS:
	MOVEI A,3			;Make index into header
	IMULM U,A
	MOVSI TAC,OUTHDR(A)
	HRRI TAC,INHDR(A)
	ENTERLOCK IMMLOK		;We need to use the mtape block
	MOVEM TAC,IMPBLK+2		;Tell system where our buffers are
	MOVEI TAC2,10
	MOVEM TAC2,IMPBLK
	MOVE TAC2,[OPEN 10,IMPBLK]	;Init IMP
	DPB U,[POINT 4,TAC2,12]
	XCT TAC2			;OPEN CHAN,IMPBLK
	JRST [	MOVEI A,[ASCIZ/Can't INIT IMP!
/]↔		LEAVELOCK IMMLOK
		JRST KLUSER ]
	LEAVELOCK IMMLOK
	MOVEI TAC,=8
	DPB TAC,[POINT 6,INHDR+1(A),11]	;Set byte size
	DPB TAC,[POINT 6,OUTHDR+1(A),11]
	MOVEM TAC,BYTSIZ(U)
	PUSH P,[2]			;Buffer buffers for IMP
	PUSH P,IMPSIZ
	PUSHJ P,MKRBUF
	MOVEM RET,INHDR(A)		;Make header for system input
	PUSH P,[2]
	PUSH P,IMPSIZ
	PUSHJ P,MKRBUF
	MOVEM RET,OUTHDR(A)		;Make header for system output
	MOVSI TAC2,(<OUTPUT 000,>)	;Initial output to set up buffers
	DPB U,[POINT 4,TAC2,12]
	XCT TAC2			;OUTPUT CHAN,
	SOS OUTHDR+2(A)			;Fix byte count
	MOVEI TAC,3			;Save initial status
	IMULM U,TAC
	MOVEI A,2
	ADD TAC2,[<MTAPE 000,A>-<OUTPUT 000,>]
	XCT TAC2			;MTAPE CHAN,[GET_STATUS...]
	MOVEM B,IMPST1(TAC)
	MOVEM C,IMPST1+1(TAC)
	ADD TAC2,[[17		;Set timeouts
		   BYTE (6) =3,=0,=30,0,0]-A]
; Bytes mean:		 CLS,RFNM,ALLOC,RFC,INP
	XCT TAC2			;MTAPE CHAN,[SET_TIMOUTS...]
	MOVE TAC2,[GETSTS 000,IMPST1+2(TAC)]	;Get I/O status
	DPB U,[POINT 4,TAC2,12]
	XCT TAC2			;GETSTS CHAN,IMPST1I+2(A)
	MOVSI TAC,400000		;Mark IMP connection as in use
	MOVN TAC2,U
	ROT TAC,(TAC2)
	ORM TAC,IMPMAP
	MOVEI TAC,MTSIZE		;Make pointer into MTAPE blocks
	IMULM U,TAC
	ADDI TAC,MTBLKS
;Graphics initial connection protocol:
;;Server program
;	listen(local = S+2, size = Bu)
;	
;listen(local = S, foreign = U+3, size = Bu)	;The foreign site specifies Bu
	MOVE TAC2,LSOCKT-NUSERS(U)	;Get local socket number
	ADDI TAC2,2
	MOVEM TAC2,LSLOC(TAC)
	PUSH P,TAC2		;Set local socket
	PUSH P,[0]		;No site
	PUSH P,[0]		;Or socket specified
	PUSH P,[8]		;8 bit connection
	PUSHJ P,NETOPN		;Open connections
;	JRST GRFEST
;Falls thru to GRFEST

>;IFN GRFPRO
BEND CONEST
SUBTTL GRFEST - Graphics Connection Established!
;Falls thru from GRFLIS
IFN GRFPRO,<
BEGIN GRFEST

↑GRFEST:
	SKIPN DEBUG
	JRST CONES2
	OUTSTR[ASCIZ/Graphics transmit side open for user #/]
	MOVE A,U
	SUBI A,NUSERS
	PUSH P,A
	PUSHJ P,TYPDEC
	OUTSTR CRLF
CONES2:	MOVE TAC,[MTAPE 000,[15↔3↔0↔0]]	;Give him default allocation
	DPB U,[POINT 4,TAC,12]
	XCT TAC				;MTAPE CHAN,[ALLOC↔3↔0↔0]
; We'll send him/her a greeting when we know what capabilities the
; terminal has.
IFN DKPRO,<
	SKIPE DKFLAG(U)		;D. King format in use?
	JRST [
		MOVEI A,[ASCIZ/Attempt to set up graphics channel
while D. King format active!  User #/]
		JRST KLUSER ]		;Flush graphics channel
	SETOM DKIACT(U)			;Enable his/her graphics channel
↑DKGINI:
>;IFN DKPRO
	SETZM DPYUSE-NUSERS(U)
	SETOM DPYFLG-NUSERS(U)		;We're ready to do display I/O
	MOVBI TAC,GREBIT+INQBIT
	ANDCAM TAC,FLAGS-NUSERS(U)
	MOVEI RET,$INQUI
	PUSHJ P,IMPOCHR
	JRST IMPOUT			;Send to IMP and return
BEND GRFEST
>;IFN GRFPRO
SUBTTL SNDGRE - Send Graphics Greeting
IFN GRFPRO,<
BEGIN SNDGRE
;
↑SNDGRE:
	SKIPN DEBUG
	JRST [	MOVE TAC,HOSTNA-NUSERS(U)
		PUSHJ P,LOGIT
		XWD 7,[ASCIZ/Greeting sent to /]
		XWD 6,TAC
		0
		JRST .+1 ]
	PUSHJ P,GET1K		;Get somewhere to put the display buffer
	PUSH P,RET		;Save address on stack to release later
	ENTERLOCK C17LOK	;Get channel 17
	INIT 17,17		;Reading GREET.DPY[NET,TVR]
	SIXBIT/DSK/
	0
	JRST LOSE
	MOVS TAC,[XWD A,[SIXBIT/GREET/
			 SIXBIT/DPY/
			 0
			 SIXBIT/NETTVR/]]
	BLT TAC,D
	LOOKUP 17,A
	JRST LOSE
	SETZ D,
	MOVE C,(P)
	HRLI C,-1024
	SUBI C,1
	INPUT 17,C
	RELEASE 17,
	LEAVELOCK C17LOK	;Release interlock on channel 17
	MOVEI C,2
	ADD C,(P)
	MOVEM C,-2(C)
	MOVEI TAC,=1024-2	;Check display buffer size
	CAMG TAC,-1(C)
	MOVEM TAC,-1(C)		;Force it to fit!
	SUBI C,2
	PUSH P,THISJOB
	PUSH P,C
	PUSH P,[1]
	PUSHJ P,IIISIM
SNDRET:	PUSHJ P,REL1K		;Release the display buffer
	POPJ P,

LOSE:	RELEASE 17,
	LEAVELOCK C17LOK	;Release channel 17
	SKIPE DEBUG
	OUTSTR[ASCIZ/Couldn't send greeting.
/]↔	JRST SNDRET

BEND SNDGRE
>;IFN GRFPRO
SUBTTL SNDIII - Send III buffer
IFN GRFPRO,<
BEGIN SNDIII
;
; Format of letter from user program
;
;LETTER: Job number of caller			;0
;	 Address of display buffer		;1
;	 POG number				;2
;	 SIXBIT/UPGIOT/				;3
;	 PTY number				;4
;	 Interrupt bit to send on completion	;5
;
↑SNDIII:PUSH P,INLET+4		;Find user number from PTY number
	PUSHJ P,FNDPTY
	JRST MAILOK		;Not found, just ignore letter.
	SETO TAC,
	CAMN TAC,INLET+5
	JRST [	MOVE TAC,PTYNUM(U)	;If -1 then halt PTY
		MOVEI TAC2,1		;HALT
		PTJOBX TAC
		JFCL			;Ignore error routine
		JRST .+1 ]
	PUSHJ P,[PUSHJ P,SCHED
		 JRST INTDIS]
	SKIPN DEBUG
	JRST NOTDEB
	OUTSTR[ASCIZ/UPGIOT, User #/]
	PUSH P,U
	PUSHJ P,TYPDEC
	OUTSTR[ASCIZ/ POG #/]
	PUSH P,INLET+2
	PUSHJ P,TYPDEC
	OUTSTR[ASCIZ/
/]
NOTDEB:	ADDI U,NUSERS		;We're doing graphics now
	PUSH P,INLET		;Save job number for later notification
	PUSH P,INLET+5		;And reply code
	PUSH P,INLET		;Args to IIISIM
	PUSH P,INLET+1
	PUSH P,INLET+2
	PUSHJ P,MAILOK		;Release mail box before calling IIISIM!
	PUSHJ P,IIISIM		;Now, send it out
	SUBI U,NUSERS		;We're done with graphics
	SETO TAC,
	CAMN TAC,(P)
	JRST [	MOVE TAC,PTYNUM(U)	;If -1 then continue PTY
		MOVEI TAC2,2		;CONT
		PTJOBX TAC
		JFCL
		JFCL
		JRST CONTED ]
	INTIPI -1(P)		;Notify calling process
	JFCL			;Ignore errors for now
CONTED:	SUB P,[XWD 2,2]		;Flush stack
	POPJ P,
BEND SNDIII
>;IFN GRFPRO
SUBTTL USECHK - Check to see if anyone's here
BEGIN USECHK
↑USECHK:
;	PUSH P,[INTIMS]		;Turn off IMP change interrupts
;	IMSKCR (P)		;(Saving old mask)
	ENTERLOCK IMCLOK	;Enter interlock against IMP change
	PUSHJ P,IMPCHG		;Check everyone's status, in case it was missed
;	IMSKST (P)		;Restore IMP interrupts
	LEAVELOCK IMCLOK	;Leave interlock against IMP change
;	POP P,(P)		;Flush old mask from stack
	PUSH P,[RUNQUE]
	PUSHJ P,RESCHED		;Run any processes invoked by IMPCHG
	SKIPN USERS		;Any users?
	jrst [	move 1,impmap
		tdnn 1,-1⊗(36-grfmul*nusers)	;Check this, too
		EXIT			;No, bye.
		pushj p,logit
		xwd 7,[asciz/USERS overSOS'ed.
USERS = 0, IMPMAP = /]
		xwd =14,IMPMAP
		xwd 7,[asciz/ USEMAP = /]
		xwd =14,USEMAP
		0
		JRST .+1]
	PUSH P,USERS		;Print number of users.
	PUSHJ P,TYPDEC
	OUTSTR[ASCIZ/ users.
/]↔
IFN CHKSW,<
	PUSHJ P,SYSCHK
>;IFN CHKSW
	PUSH P,[USECHK]		;Start up again in two minutes
	PUSH P,[2*=60*=60]
	PUSHJ P,DELAY
	POPJ P,
BEND USECHK
IFN DEBPRC,<
TEST1:	OUTCHR["1"];
	POPJ P,
TEST2:	OUTCHR["2"];
	POPJ P,
TEST3:	OUTCHR["3"];
	POPJ P,
TEST4:	OUTCHR["4"];
	POPJ P,
>;IFN DEBPRC
SUBTTL STATUS - Print status of NETGRF
BEGIN STATUS

;Status obtained when via mail
↑XSTAT:	PUSH P,INLET		;Make block with which to send interrupt
	PUSH P,INLET+5
	MOVE TAC,INLET+4	;Get name of TTY
	PUSHJ P,MAILOK		;Release mailbox
	PUSHJ P,STAT1		;Use that as TTY number
	INTIPI -1(P)		;Notify calling process
	JFCL			;Ignore errors for now
	SUB P,[XWD 2,2]		;Flush stack
	POPJ P,

;Status obtained when typing <esc>I
↑STATUS:MOVSI TAC,'TTY'
STAT1:	MOVEM TAC,STSDEV
	MOVEI 1,=80		;Get byte count
	MOVEM 1,STSCNT
	PUSHJ P,FLUSH		;Force setup of buffers
	CALL WRASCZ,<[[ASCIZ/Status of NETGRF on /]]>,STSOP
	ACCTIM RET,		;Include date and time
	CALL WRDAYT↑,RET,STSOP
	CALL WRASCZ,<[[ASCIZ/
/]]>,STSOP
	CALL WRINT,USERS,<[=10]>,STSOP
	CALL WRASCZ,<[[ASCIZ/ users.
Active processes:
Queue	    User# PBlock Datum  PC
/]]>,STSOP
	MOVEI E,BEGQUE		;Start looking at queues
QUECHK:	SKIPN A,(E)		;Anything there?
	JRST QEMPTY
	CALL WRSYMB↑,E,STSOP	;First time thru, print name
	SKIPA 1,["	"]	;Then print <tab>
QLOOP:	XCT STSOP
	XCT STSOP
	HRRE 1,%USER(A)		;Print user number
	CALL WRINT,1,<[=10]>,STSOP
	MOVEI 1," "		;Then print <space>
	XCT STSOP
	CALL WROCT,A,<[6]>,STSOP ;Print Process Block number
	MOVEI 1," "		;Then print <space>
	XCT STSOP
	HLRE 1,%DATUM(A)	;Print datum number
	CALL WRINT,1,<[8]>,STSOP
	MOVEI 1,"	"	;Then print <tab>
	XCT STSOP
	HRRZ 1,%PACS(A)		;Get address of ACs
	JUMPE 1,QFOO
	HRRZ 1,-1(1)		;Print PC
	CALL WRSYMB↑,1,STSOP
QFOO:	CALL WRASCZ,<[[ASCIZ/
/]]>,STSOP
	HLRZ A,%LINK(A)		;Pick up next block
	CALL RESCHED,<[RUNQUE]>
	MOVEI 1,"	"	;Maybe print another <tab>
	JUMPN A,QLOOP
QEMPTY:	ADDI E,QUESIZ		;Advance to next queue
	CAIGE E,BEGQUE+NQUES*QUESIZ	;Done yet?
	JRST QUECHK		;No, print next one
	PUSHJ P,FLUSH		;Flush out input buffer
	TURNON [INTTTI]		;Re-enable <esc>I
	POPJ P,

STSOP:	PUSHJ P,STSPUT		;Opcode to pass to convertion routines

;Output a character to arbitrary TTY
STSPUT:	JUMPE 1,CPOPJ		;Don't even look at nulls!
	SOSGE STSCNT		;Room in buffer?
	PUSHJ P,FLUSH1		;No, flush output buffer first
	IDPB 1,STSPTR		;Stick it in buffer
CPOPJ:	POPJ P,
;Flush output buffer
FLUSH:	SOS STSCNT		;Compensated later
	PUSHJ P,FLUSH1
	AOS STSCNT
	POPJ P,
FLUSH1:	PUSH P,1		;Save an AC
	MOVE 1,[POINT 7,STSBUF]	;Point at next buffer
	MOVEM 1,STSPTR
	MOVEM 1,STSDEV+1	;Set pointer for TTYMES
	MOVEI 1,=80-1		;Get byte count
	SUB 1,STSCNT
	ADDM 1,STSCNT		;Reset byte count
RETRY:	DPB 1,[POINT 12,STSDEV+1,17]	;Store it away for TTYMES
	JUMPE 1,NONE
	MOVEI 1,STSDEV		;Output to arbitrary teletype
	NULMES 1,
	JUMPN 1,[PUSH P,1	;Failed, advance byte pointer to account
		PUSH P,2	;for characters actually sent
		CALL POSTPONE,<[5]>;Postpone action a bit to give system time to work
		LDB 1,[POINT 12,STSDEV+1,17]	;Pick up old byte count
		SUB 1,-1(P)	;Calculate number of bytes to advance
		IDIVI 1,5	;Seperate into words and bytes
		ADDM 1,STSDEV+1	;Increment word part
		JUMPE 2,NOADV
		MOVEI 1,700	;Remake 7 bit byte pointer
		DPB 1,[POINT 12,STSDEV+1,17]
	ADVPTR:	IBP STSDEV+1
		SOJG 2,ADVPTR
	NOADV:	POP P,2
		POP P,1
		JRST RETRY ]
NONE:	POP P,1
	POPJ P,

INTEGER STSCNT,STSPTR
ARRAY STSDEV[2],STSBUF[=80/5]

BEND STATUS
SUBTTL CHGJOB - Change job paramenters
;DETJOB	- Detach job from hung net connection
;D.KING - Specify D. King's format
;
↑CHGJOB:MOVE C,A		;Save command
	MOVE A,INLET+4		;PTY number to be detached from
	MOVE B,INLET		;Job number of requestor
	PUSHJ P,MAILOK		;Release mailbox
	SKIPN DEBUG
	JRST CHGJB2
	PUSH P,C		;Print code
	PUSH P,[OUTCHR 1]
	PUSHJ P,WRSIX↑
	OUTSTR[ASCIZ/
/]
CHGJB2:	PUSH P,A		;Get user number from PTY number
	PUSHJ P,FNDPTY
	POPJ P,			;Not one of ours
	CAMN C,[SIXBIT/DETJOB/]
	JRST [	MOVEI B,[BYTE (9) 600,600,"D","E","T",15,12,0]	;Send ↑C ↑C DET<CR><LF>
		PTWRS9 A		;and hope we don't hang!!!
		POPJ P, ]
IFN DKPRO,<
	CAMN C,[SIXBIT/D.KING/]	;D. King's crockish format??
	JRST [	SETOM DKFLAG+NUSERS(U)	;Just turn on flag and hope for the best!!!
		SETOM DKIACT(U)		;Mark TELNET channel as being active
		ADDI U,NUSERS		;GET INTO GRAPHICS MODE
		JRST DKGINI ]
>;IFN DKPRO
	POPJ P,
SUBTTL OUTWHR - Write out WHERE table for MAIL, FINGER, etc.

OUTWHR:	PUSH P,TAC
	MOVE TAC,[XWD 3,[XWD 'NET',0
			IOWD WHRLEN,WHRTAB]]
	TMPCOR TAC,
	JFCL
	POP P,TAC
	POPJ P,
;-------------------------------------------------
SUBTTL INTSER - Interrupt Service Routine
BEGIN INTSER

;The system sets up the following:
SWBUT	←← 1		;Spacewar buttons
PROREL	←← 2		;Protection/Relocation
SWPWRN	←← 3		;Swap or shuffle warning
PROCNO	←← 4		;Processor number (1. PDP-10, 2. PDP-6)
OTHSTA	←← 5		;Status of other processor
JSTAT	←← 6		;Job status
HIREL	←← 7		;Size of upper - 1
DATUM	←← 10		;Interrupt datum
JQUE	←← 14		;System queue number

↑INTSER:MOVE P,IPDLIOWD		;Get a PDL
	AOS INTCNT		;Count the interrupts
IFN DEBPRC,<
	aos foodpy
	aos foodpy
>;IFN DEBPRC
	MOVE TAC,JOBCNI↑
	JFFO TAC,INTSE2
IFN DEBPRC,<
	SKIPN DEBUG		;Spurious interrupt
	DISMIS
	MOVE TAC,[ASCID/-RAND/]
	MOVEM TAC,NAMLOC
	SKIPGE DEBUG
	UPGIOT 16,INTDPY
>;IFN DEBPRC
	DISMIS
INTSE2:	SETOM INTLEV
	ROT TAC2,-1		;Makes table smaller
	JUMPGE TAC2,INTSE3	;Even words
	SKIPA TAC,INTTAB(TAC2)
INTSE3:	MOVS TAC,INTTAB(TAC2)
IFN DEBPRC,<
	SKIPN DEBUG
	JRST (TAC)
	PUSH P,-1(TAC)
	POP P,NAMLOC
	SKIPGE DEBUG
	UPGIOT 16,INTDPY
>;IFN DEBPRC
	JRST (TAC)		;Call routine
INTTAB:	XWD BADINT,BADINT	;INTSWW,,INTSWD
	XWD BADINT,BADINT	;INTSHW,,INTSHD
	XWD BADINT,PTIREQ	;INTTTY,,INTPTI
	XWD RDMAIL,BADINT	;INTMAIL,,INTWAIT
IFN CHKSW,<
	XWD PTOREQ,PARSER	;INTPTO,INTPAR
>;IFN CHKSW
IFE CHKSW,<
	XWD PTOREQ,BADINT	;INTPTO,INTPAR
>;IFE CHKSW
	XWD CLKSER,BADINT	;INTCLK,,INTINR
	XWD BADINT,IMPSTC	;INTINS,,INTIMS
	XWD GTIMPI,ESCI		;INTINP,,INTTTI
	XWD BADINT,BADINT	;INTQXF,,bit 17
	XWD BADINT,BADINT	;bit 18,,POV
	XWD BADINT,BADINT	;bit 20,,bit 21
	XWD BADINT,BADINT	;ILM,,NXM
	XWD BADINT,BADINT	;bit 24,,bit 25
	XWD BADINT,BADINT	;OLDCLK,,bit 27
	XWD BADINT,BADINT	;bit 28,,INTFOV
	XWD BADINT,BADINT	;bit 30,,bit 31
	XWD BADINT,BADINT	;INTOV,,bit 33
	XWD BADINT,BADINT	;bit 34,,bit 35
↑INTDIS:IMSKCL 1,JOBCNI
↑INTRET:SKIPN INTLEV
	JRST [	MOVE TAC,@OLDPC		;Were we at an interrupt wait
		AND TAC,[XWD 777000,777777]	;instruction?
		CAME TAC,[IWAIT]
		CAMN TAC,[IENBW]
		AOS OLDPC		;Yes, increment PC
		MOVE TAC,@OLDPC
		AND TAC,[XWD 777740,0]
		CAMN TAC,[IMSTW]
		AOS OLDPC
		MOVSI P,OLDACS
		BLT P,P
		INTJEN OLDMSK ]
	SETZM INTLEV
	DISMIS

	ASCID/NTBAD/
BADINT:	MOVE TAC2,JOBCNI	;Oops, what are we doing here!!!
	MOVEM TAC2,INTLOS
	JRST INTDIS

	ASCID/NTTTI/
ESCI:	OUTSTR[ASCIZ/<esc>I/]
	seto u,
	push p,[status]
	pushj p,sched
	JRST INTRET

; We got some mail, queue up process to read it
;	ASCID/NTMAI/
;MAIL:	PUSH P,[RDMAIL]		;Queue up process to read mail
;	PUSHJ P,SCHED
;	JRST INTDIS		;Disable mail interrupt and return

; IMP status changed, let's see how
	ASCID/NTIMS/
IMPSTC:
IFN DEBPRC,<
	SKIPE DEBUG
	OUTCHR["*"]
>;IFN DEBPRC
	PUSH P,[IMPCHG]
	PUSHJ P,SCHED
	JRST INTRET

; Some IMP connection has input for us
	ASCID/NTINP/
GTIMPI:	MOVSI U,-GRFMUL*NUSERS	;We get to figure out which connection did it
	SETOM ALLINP		;Assume all pty input buffer are full unless we
				;find out otherwise
	jfcl			;Room to patch in PUSHJ P,USERMODE
	AOS IMICNT
IMLOOP:	SKIPN PTIFUL(U)		;Are we not already trying to stuff things into that PTY
	SKIPN PTYNUM(U)		;and is there a PTY for that job?
	  JRST NOINP		;No, don't look then
REPEAT 0,<
IFE DKPRO,<
	MOVE TAC,[MTAPE 000,[10]]	;Is there any input present here?
	DPB U,[POINT 4,TAC,12]
>;IFE DKPRO
IFN DKPRO,<
	SKIPN DKIACT(U)		;Is channel active?
	  JRST NOINP		;  Not active, skip it
	MOVE A,U		;Assume regular channel
	SKIPE DKFLAG(U)		;If in DK mode
	  SUBI A,NUSERS		;  check TELNET channel instead
	MOVE TAC,[MTAPE 000,[10]]	;Is there any input present here?
	DPB A,[POINT 4,TAC,12]
>;IFN DKPRO
	MOVEM TAC,LSTIMC#	;*** For debugging ***
	XCT TAC			;MTAPE CHAN,[INPSKP]
>;REPEAT 0
;	PUSH P,[MTAPE 000,[10]]	;Is there any input present here?
IFE DKPRO,<
	DPB U,[POINT 4,FOO,12]
>;IFE DKPRO
IFN DKPRO,<
	SKIPN DKIACT(U)		;Is channel active?
	  JRST NOINP		;  Not active, skip it
	MOVE A,U		;Assume regular channel
	SKIPE DKFLAG(U)		;If in DK mode
	  SUBI A,NUSERS		;  check TELNET channel instead
	DPB A,[POINT 4,FOO,12]
>;IFN DKPRO
	MOVEM TAC,LSTIMC#	;*** For debugging ***
;	XCT (P)			;MTAPE CHAN,[INPSKP]
;The following instruction gets error message 'IO TO UNASSIGNED CHANNEL AT USER XXX'
;under certain unknown circumstances which typically occur when the machine is
;lightly loaded.  Self modifying code only because one's AC's vanish when you
;die at user interrupt level!
FOO↑:	MTAPE 000,[10]		;Is there any input present?
	JRST [	SETZM ALLINP	;No, remember so we can interrupt again
;;;		POP P,(P)
		JRST NOINP ]
;;;	POP P,(P)
	SETOM PTIFUL(U)		;Mark as busy
	PUSH P,U		;Look for a process waiting for IMP input
	HRRZS (P)		;(We just want the user number)
	PUSH P,[IMWQUE]
	PUSHJ P,SRHQUE
	JUMPN RET,[		;Yes, give him the interrupt
		PUSH P,RET		;Give him good service (could be ↑C)
		PUSH P,[PRIQUE]
		PUSHJ P,ENQUE
		AOS RUNWAIT		;Another process waiting for service
		JRST NOINP ]
IFN GRFPRO,<
	HRRZ A,U
	IDIVI A,NUSERS		;No one waiting, create a process to handle it
	PUSH P,[IMISER↔GRISER](A);of the appropriate flavour
>;IFN GRFPRO
IFE GRFPRO,<
	PUSH P,[IMISER]		;No one waiting, create a process to handle it
>;IFE GRFPRO
	PUSHJ P,SCHED
NOINP:	AOBJN U,IMLOOP		;Try next user
	SKIPN ALLINP		;Are all the buffers full?
	  JRST INTRET		;No, then just return
	JRST INTDIS		;Yes! Don't waste everyone's time checking
				;until one become available
; Some PTY is waiting for input
	ASCID/NTPTI/
PTIREQ:	MOVSI U,-NUSERS		;We get to figure out which PTY(s) did it
	jfcl
	AOS INPCNT
PILOOP:	SKIPN PTBUSY(U)		;Are we waiting to stuff things into that PTY
	JRST NXTPTY		;No, don't look then
	PUSH P,[PTICHK]
	PUSHJ P,SCHED
NXTPTY:	AOBJN U,PILOOP		;Try next user
	SETZM PTYIWA		;Forget we were waiting
;*** WHERE IS PTYIWA CHECKED? ***
	JRST INTDIS

; Some PTY has output for us.
	ASCID/NTPTO/
PTOREQ:	MOVSI U,-NUSERS		;We get to figure out which one!
POLOOP:	SKIPE PTYNUM(U)		;Is there no PTY for this user
	SKIPE PTOFUL(U)		;or is there is output going on already?
	JRST POCONT		;Yes, no need to make new process
	SETOM PTOFUL(U)
	PUSH P,[PTOSER]
	PUSHJ P,SCHED
POCONT:	AOBJN U,POLOOP		;More to go?
	JRST INTRET		;No, return

IFN CHKSW,<
	ASCID/NTPAR/
PARSER:	MOVE 16,[IOWD 3,PARMSG+2]
	PUSH 16,[XWD =13,0]	;Compile:  Address of error
	HRRM DATUM,(16)
	PUSH 16,[XWD =14,0]	;Compile:  Prot-reloc
	PUSH P,PROREL
	HRRM P,(16)
	PUSH 16,[XWD =14,0]	;Compile:  Value
	PUSH P,(DATUM)
	HRRM P,(16)
	PUSHJ P,USERMODE	;Get into user mode, quick!
	PUSHJ P,SYSCHK
	PUSHJ P,PARMSG
	JRST INTRET
>;IFN CHKSW
BEND INTSER
SUBTTL CLKSER - Service clock interrupt
BEGIN CLKSER
;
; ***  Warning:  This code runs at interrupt level!  ***
;
	ASCID/NTCLK/
↑CLKSER:AOS CLKCNT		;Number of clock interrupts.
IFN DEBPRC,<
	SKIPE DEBUG
	OUTCHR ["⊗"]
>;IFN DEBPRC
	TIMER A,		;Pick up current time
	SUB A,NXTTIM		;Compare with time expected.
	ADDM A,CLTDIF		;Remember total amount off for fun.
	JUMPL A,[ MOVN A,A
		  CLKINT 1,(A)		;Next time
		  AOS CLKBAD		;Increment number of losing interrupts!
		  JRST CLKEXI ]		;Don't ever run anything early!
ANOTHER:PUSH P,[CLKQUE]		;Get first entry in clock queue
	PUSHJ P,DEQUE
	JUMPE RET,[PUSHJ P,DRYROT	;Clock interrupt with no process to run!
		   JRST QUIET]
	PUSH P,RET		;Queue it to be run
	PUSH P,[PRIQUE]		;Priority service for clock interrupts.
	PUSHJ P,ENQUE
IFN DEBPRC,<
	SKIPE DEBUG
	OUTCHR ["+"]
>;IFN DEBPRC
	AOS RUNWAIT		;Increment number of processes waiting to be run
	SKIPN TAC,CLKQUE	;Get pointer to next process
	JRST QUIET		;Last process
	HLRZ TAC2,%DATUM(TAC)	;Get incremnt to clock
	ADDM TAC2,NXTTIM
	SUB TAC2,A
	JUMPLE TAC2,ANOTHER	;It happened already!!!
	CLKINT 1,(TAC2)		;When to return
	JRST CLKEXI
QUIET:	CLKINT 1,0		;Turn off clock interrupts
	SETZM NXTTIM
CLKEXI:
IFN DEBPRC,<
	SKIPE DEBUG
	OUTCHR ["≥"]
>;IFN DEBPRC
	JRST INTRET		;And return
	
BEND CLKSER
SUBTTL IMPCHG - IMP status change
BEGIN IMPCHG
;
; Check status of IMP connection, invoking processes to take care of
; them.  This routine is primarily designed to run at interrupt level.
;
; Assume it destroys all acs except P
;
↑IMPCHG:MOVSI U,-GRFMUL*NUSERS-NSPECU	;User number
	MOVEI A,IMPST1		;Index for saving IMP status
	MOVE B,[GETSTS 000,E]
	MOVSI C,400000		;Bit mask for IMP I/O present
	MOVEI D,2		;Opcode for GET STATUS
ISLOOP:	TDNN C,IMPMAP		;Is there an IMP here?
	  JRST NOTIMP
	XCT B			;Get status of I/O
	EXCH E,2(A)
;	MOVEM E,OLDST1-IMPST1+1(A)	;See what bits came on
	ANDCA E,2(A)
	TLNE E,HDEAD!CTROV!RSET!TMO	;Anything indicating great lossage
	JRST [	PUSH P,A
		TLNE E,TMO
		MOVEI A,[ASCIZ/Timeout. Connection closed. /]
		TLNE E,CTROV
		MOVEI A,[ASCIZ/Host overflow allocation. Connection closed. /]
		TLNE E,HDEAD
		MOVEI A,[ASCIZ/Host dead. /]
		TLNE E,RSET
		MOVEI A,[ASCIZ/Host sent reset. Connection closed. /]
		PUSH P,[KLUSER]
		PUSHJ P,SCHED
		JRST NOTIMP ]
	ADD B,[<MTAPE 000,D>-<GETSTS 000,E>]
	XCT B			;Get status of rcv and xmit connections
	ADD B,[<GETSTS 000,E>-<MTAPE 000,D>]
	EXCH E,(A)		;Save new status and get old
	EXCH F,1(A)
;	MOVEM E,OLDST1-IMPST1(A)
;	MOVEM F,OLDST1-IMPST1+1(A)
	ANDCA E,(A)		;Note bits were turned on
	ANDCA F,1(A)
	TLNN F,CLSR		;Close recieved?
	TLNE E,CLSR
	  JRST CLOSIT		;Yes, reply with close
	ORM E,CHGFLG(U)		;Mark bits which changed
	MOVSS F
	ORM F,CHGFLG(U)
	MOVSS F
	PUSH P,U		;Look for a process waiting for IMP status change
	HRRZS (P)		;(We just want the user number)
	PUSH P,[IMSQUE]
	PUSHJ P,SRHQUE
	JUMPN RET,[		;Yes, give process the interrupt
		PUSH P,RET		;Give it good service
		PUSH P,[PRIQUE]
		PUSHJ P,ENQUE
		AOS RUNWAIT		;Another process waiting for service
		JRST NOTIMP ]
NOTIMP:	ADDI A,3		;Increment status pointer
	ADD B,[1B12]		;Increment channel in MTAPE
	ROT C,-1		;Move test bit
	AOBJN U,ISLOOP		;More to come
	POPJ P,

	
CLOSIT:	PUSH P,A		;Save some acs
	PUSH P,U
	HRRZ U,U		;Flush count from left half
	CAIN U,U.ICP		;Is it ICP listen being closed?
	  SKIPA A,[CLOICP]	;  Yes, start new listen
	MOVEI A,KLUSER		;No, flushing user connection
	PUSH P,A		;Process to handle close
	MOVEI A,[ASCIZ/Connection closed. /]	;Message for KLUSER
	PUSHJ P,SCHED
	POP P,U
	POP P,A
	JRST NOTIMP

BEND IMPCHG
SUBTTL PTYSER - Service PTY
BEGIN PTYSER

	ASCID/NPTO/
↑PTOSER:MOVE A,PTYNUM(U)	;Get PTY number
IFN DEBPRC,<
	SKIPE DEBUG
	outchr ["←"]
>;IFN DEBPRC
IFN SPYSW,<
	PUSHJ P,SPY
>
POLOOP:
	ENTERLOCK PTOLOK	;Enter interlock for PTY's
	PTOCNT A		;Make sure we don't hang waiting
	JUMPE B,NOREAD		;Oops, nothing left
	LEAVELOCK PTOLOK	;Leave interlock for PTY
	SKIPN B,PTOBUF(U)
	PUSHJ P,DRYROT
	PTRDS A			;Read a string from PTY
IFN IMLSW,<
	SKIPE IMLACT(U)		;Imlac mode?
	JRST IMLHAK		;  Yes, hack it before sending it!
>;IFN IMLSW
	PUSH P,B
	PUSHJ P,IMPSTR		;Output string to IMP
POCONT:	PUSH P,[RUNQUE]
	PUSHJ P,RESCHED		;Give others a chance
	JRST POLOOP
NOREAD:	SETZM PTOFUL(U)		;We're no longer looking for output, tell
	LEAVELOCK PTOLOK	;Leave interlock for PTY
	TURNON [INTPTO]		;Enable INTPTO
IFN DEBPRC,<
	SKIPE DEBUG
	outchr ["→"]
>;IFN DEBPRC
CPOPJ:	POPJ P,


↑IMISER:MOVEI A,3		;Pointer into INHDR
	IMULM U,A
	MOVE B,PTYNUM(U)	;Get PTY number
IFN DEBPRC,<
	SKIPE DEBUG
	outchr[173]		;open curly bracket!!!!
>;IFN DEBPRC
CILOOP:	PUSHJ P,IMICHS		;Check for character from IMP ready
IFE DEBPRC,<
	JRST [	SETZM PTIFUL(U)		;None, clear flag saying being serviced
		TURNON [INTINP]
		POPJ P, ]
>;IFE DEBPRC
IFN DEBPRC,<
	JRST [	SETZM PTIFUL(U)		;None, clear flag saying being serviced
		TURNON [INTINP]
		skipe debug
		outchr[176]	;close curly bracket!!!!
		POPJ P, ]
>;IFN DEBPRC
	CAIL RET,200		;Protocol perhaps?
	JRST SPCHAR		;Handle it
NOTSP:	PUSHJ P,STUFF		;Try to stuff it at the PTY
	JRST CILOOP		;Success, try another
	PUSH P,[=15]		;Wait a little and try again
	PUSHJ P,POSTPONE
	PUSHJ P,STUFF		;Try to send it again
	JRST CILOOP		;Success, try another
	PUSH P,[=60]		;Lose again, try one more time!
	PUSHJ P,POSTPONE
IRETRY:	PUSHJ P,STUFF		;Try to send it again
	JRST CILOOP		;Finally! Now, try another
	MOVEM TAC,PTBUSY(U)	;This is a hard core case, wait until we get
				;interrupt saying the PTY thinks it's ready 
	SETOM PTYIWA		;Remember that someone is waiting for PTY
				;input.  USECHK looks at this flag.
	TURNON [INTPTI]
	POPJ P,
↑PTICHK:MOVEI A,3		;Pointer into INHDR
	IMULM U,A
	MOVE B,PTYNUM(U)	;Get PTY number
	MOVE C,PTBUSY		;Get character we were trying to stuff
	JRST IRETRY

STUFF:	MOVE C,RET
	PTWR1S B
;	AOS (P)
;	POPJ P,
	jrst [	aos(p)
		popj p,]
;	skipe debug
;	outchr c
	popj p,

SPCHAR:
IFN NEWPRO,<	CAIN RET,$IAC	;New protocol commands
	JRST DOIAC	>
	ANDI RET,177		;Otherwise, map onto ASCII (sigh...)
	JRST NOTSP
SUBTTL IMLHAK - Hack Imlac compatability mode
IFN IMLSW,<
BEGIN IMLHAK
COMMENT ⊗
NETGRF pretends it's an Imlac in Extended-ASCII mode so that all
characters can be handled (since β is ↑C, etc.)

[The following was extracted from IMLHAK.BO[UP,DOC] on 3-Sep-75]

SUMMARY OF COMMANDS TO THE IMLAC (all prefixed with a rubout)

	OCTAL NO. OF
CHAR	CODE  OPERANDS	FUNCTION

λ	 010	0 __	Print a center dot (the hidden code for null)
TAB	 011	0   \
LF	 012	0    |
VT	 013	0    |	Print  that char  the  way  DD does  it  when
FF	 014	0    |	prefixed with a rubout.
CR	 015	0 __/
∞	 016	0	No-op
∂	 017	0	Print a BS char as on the DD.

1-7	 060+n	n	Enter re-edit mode, clear the  edit buffer if
			mode wasn't  re-edit, and add the following n
			characters to the  edit line  as if the  user
			had typed  them. Re-edit  mode locks  out the
			IMLAC  keyboard except the  CALL, ESC, BREAK,
			and CLEAR keys.

0	 060	0	Enter line mode.   Used to  terminate re-edit
			mode  and leave the  cursor at  the right end
			of the line.

∧	 004	0	Move the cursor to the left  end of the line.
			This  command   does  not  terminate  re-edit
			mode.

¬	 005	0	Same as 004, but terminates re-edit mode.

ε	 006	1	Move the cursor right the  number of positions
			specified by  the octal code of the following
			character, then terminate re-edit mode.

π	 007	0	Clear edit line, terminate re-edit.

↓	 001	0	INCHRW.  If the edit buffer  is not empty and
			the cursor  is not at the  left end, send the
			first char from the  buffer.  Otherwise,  set
			the break table to  activate on the next char
			typed.

α	 002	0	DDT  mode  INCHWL.     If  the   edit  buffer
			contains  a word-mode break char  to the left
			of the cursor,  then send  the first part  of
			the  buffer up  to  and  including the  first
			such  char.   Otherwise, set  the break table
			to activate on the next such char typed.

β	 003	0	Enter character mode.

The IMLAC reverts to  line mode after complying with the  001 and 002
commands. (These commands are no-ops in Character mode.)
⊗;

↑IMLHAK:PUSH P,A	;Save for safety
	HRLI B,(<POINT 7,0>)	;Make into byte pointer
IMLHA2:	MOVE A,B	;Copy byte pointer
IMLHA3:	ILDB RET,B	;Get character
IMLHA4:	JUMPE RET,[PUSH P,A
		   PUSHJ P,IMPSTR
		   JRST IMLDON]
	CAIE RET,177
	JRST IMLHA3
	SETZ RET,	;Replace 177 with null to terminate string
	DPB RET,B
	PUSH P,B	;Send string upto where 177 was seen
	PUSHJ P,IMPSTR
	ILDB RET,B	;Pick up command character
	CAILE RET,IMLLEN	;Jump table opcode?
	JRST IMLHA5		;Maybe line edit
	PUSHJ P,@IMLTAB(RET)	;Execute operation
	JRST IMLHA2		;Back for more
IMLHA5:	CAIL RET,060	;Re-edit?
	CAILE RET,067
	JRST [	PUSHJ P,IMLBAD	;No, give up and complain
		JRST IMLHA2 ]	;Then try again
;For now, will ignore re-edit commands and just pass on text
	MOVE B,A		;Copy byte pointer
	JRST IMLHA4		;And just send as normal text for now
IMLDON:	POP P,A
	JRST POCONT
IMLTAB:	IMLBAD		;000	Can't happen this way!!!
	CPOPJ		;001 ↓	INCHRW [no-op]
	CPOPJ		;002 α	DDT mode INCHWL [no-op]
	CPOPJ		;003 β	Enter character mode [no-op]
	CPOPJ		;004 ∧	Do local <control><form> [no-op]
	CPOPJ		;005 ¬	Same as 004, but terminates re-edit setup. [no-op]
	[IBP B		;006 ε	Move cursor right by arg. positions and
CPOPJ:	 POPJ P,]	;	terminate re-edit setup. [no-op]
			;007 π	Clear edit line, terminate re-edit setup. [no-op]
	[MOVEI RET,0	;010 λ	Print a center dot (hidden null)
	 JRST IMPOCHR]
	IMPOCHR		;011 TB	Print TB like DD [just send character for now]
	IMPOCHR		;012 LF	Print LF like DD [just send character for now]
	IMPOCHR		;013 VT	Print integral sign [just send character for now]
	IMPOCHR		;014 VT	Print plus-minus sign [just send character for now]
	IMPOCHR		;015 CR	Print CR like DD [just send character for now]
	CPOPJ		;016 ∞	No-op
	[MOVEI RET,177	;017 ∂	Print BS like DD [send one]
	 PUSHJ P,IMPOCHR]
IMLLEN←←.-IMLTAB

;Bad Imlac command
IMLBAD:	MOVE C,RET	;Copy somewhere safe
	PUSHJ P,LOGIT
	XWD 7,[ASCIZ/Bad Imlac command: /]
	XWD =8,C
	0
	POPJ P,
BEND IMLHAK
>;IFN IMLSW
SUBTTL NEWPRO - Interpet new TELNET protocol
IFN NEWPRO,<
DOIAC:	PUSHJ P,IMICHW		;Next byte is command
	CAIL RET,NPLOW		;Within range?
	JRST @NPTAB-NPLOW(RET)	;No! return
	JRST CILOOP
NPLOW←←$SE
NPTAB:	CILOOP			;SE	End of subnegotiation
	CILOOP			;NOP
	CILOOP			;DATAM	Datam mark
	STOPJOB			;BREAK	Break (↑C)
	STOPJOB			;IP	Interrupt process
	CLROUT			;AO	Abort output
	SNDWHO			;AYT	Are you there? (Sends who line)
;The following two are done wrong in Imlac mode (IMLSW ∧ IMLACT(U)
	[MOVEI RET,177		;EC	Erase character
	 JRST NOTSP]
	[MOVEI RET,"U"-100	;EL	Erase line
	 JRST NOTSP]
	CILOOP			;GA	Go ahead
	SUBNEG			;SB	Subnegotiation begin
	NEGOTIATE		;WILL	Desire to begin performing or confirmation
				;373	of an option
	NEGOTIATE		;WONT	Refusal to begin performing or continue
				;374	an option
	NEGOTIATE		;DO	Request for other party to performing or
				;375	confirmation of that you are expecting
				;	the performation of an option
	NEGOTIATE		;DONT	Request for other party to performing or
				;376	confirmation of that you are expecting
				;	the performation of an option
	NOTSP			;IAC	Quote an IAC
STOPJOB:
	MOVEI C,7		;Clear PTY's input buffer
	PTJOBX B
	MOVEI C,600		;Send a <CALL> (send two 600's)
	PTWR1W B
STOP2:	MOVEI RET,600
	JRST NOTSP
CLROUT:;SETO C,			;No interrupts while we flush stuff in
;	IMSKCR C		;output buffer
	ENTERLOCK IMOLOK	;Interlock again IMP output
	HRRZ TAC,OUTHDR(A)	;Make a new byte pointer
	HLL TAC,OUTHDR+1(A)
	TLZ TAC,770000		;Reset to last byte in word previous to data
	MOVEM TAC,OUTHDR+2(A)
	LSH TAC,-=24		;Pick up byte size
	MOVEI TAC2,=36		;Number of bytes per word
	IDIVM TAC2,TAC
	HLRZ TAC2,OUTHDR(A)	;Number of words
	TLZ TAC2,400000
	SUBI TAC2,1
	IMUL TAC,TAC2		;Total number of bytes
	MOVEM TAC,OUTHDR+2(A)
;	INTMSK (C)		;Restore interrupts
	LEAVELOCK IMOLOK	;Interlock again IMP output
	MOVEI RET,10000+"O"	;Send PTY a <ESC>O
	JRST NOTSP
; Are You There (respond with WHO line)
SNDWHO:;	MOVBI TAC,WHOBIT
	movsi tac,whobit		;*** Fix that macro ***
	TDNE TAC,FLAGS(U)	;In a WHO line already?
	JRST CILOOP		;Yes, ignore
	ORM TAC,FLAGS(U)	;No, enter one then (timing race not important
				;here, so what if he requests too many WHOs)
	PUSHJ P,MKPBLK
	MOVE C,RET		;Save address of block
	MOVE D,RET
	SUBI D,PROCSZ-2		;Point to second word
	HRRZ TAC,B		;Get number of controling job
	TTYJOB TAC,
	SKIPG TAC		;Get system WHO line if no job
	SETO TAC,
	HRL D,TAC
	WHO D,			;Get WHO line
	HRRZ D,D
	PUSH P,[WHOOUT]		;Make process to output who
	PUSHJ P,SCHED
	PUSH P,[RUNQUE]
	PUSHJ P,RESCHED
	JRST CILOOP
WHOOUT:	PUSH P,D
	PUSHJ P,IMPSTR		;Send WHO line
	PUSH P,C
	PUSHJ P,KLPBLK
	MOVSI TAC,WHOBIT
	ANDCAM TAC,FLAGS(U)
	POPJ P,

SUBNEG:	PUSHJ P,IMICHW		;Get option number
IFN IMLSW,<
	CAIN RET,$EXTASC	;Extended ASCII?
	JRST DOEXTA		;  Yes, go off to deal with it
>;IFN IMLSW
;Unknown option subnegotiation, look for IAC SE
IGNSUB:	PUSHJ P,IMICHW		;Get a character
	CAIE RET,$IAC		;Command?
	JRST IGNSUB		;No, try again
	PUSHJ P,IMICHW		;Which one?
	CAIN RET,$SE		;End of Subnegotiation?
	JRST CILOOP		;Yes, done
	CAIL RET,$SB		;No, ignore command. Another byte to get?
	CAILE RET,$DONT
	JRST IGNSUB		;No, back to looking for IAC
	PUSHJ P,IMICHW
	JRST IGNSUB
;Other party wants something changed.
NEGOTIATE:
	MOVE C,RET		;Save command
	SKIPE DEBUG
	  OUTSTR@[[ASCIZ/WILL /]
		  [ASCIZ/WON'T /]
		      [ASCIZ/DO /]
		      [ASCIZ/DON'T /] ]-$WILL(C)
	PUSHJ P,IMICHW
	MOVE D,RET		;Save option number
	SKIPN DEBUG
	  JRST NEGOT2
	PUSH P,D
	PUSHJ P,TYPDEC
NEGOT2:	CAIN D,$ECHO		;Echo option?
	  JRST @ECHOS-$WILL(C)
IFN IMLSW,<
	CAIN D,$EXTASC		;Extended ASCII?
	  JRST @EXTAST-$WILL(C)
>;IFN IMLSW
	CAIN D,$SGA		;Agree with whatever they say, and then lie
	  JRST ACKPOS		;through our teeth and never send GA!!!
;We don't recognize that option, send negative acknowledgement of option
ACKNEG:	TRNE C,1		;Make WILL or DO into WONT or DONT
	ADDI C,1
	TRC C,2			;Change WONT ↔ DONT
SNDACK:	MOVSI TAC,400000	;See if this is a confirmation?
	MOVN RET,D
	LSH TAC,(RET)
	TDNE TAC,PROMAP(U)
	JRST [	ANDCAM TAC,PROMAP(U)	;Yes, don't send them another!
		JRST CILOOP ]
	MOVEI RET,$IAC		;Begin command
	PUSHJ P,IMPOCH
	MOVE RET,C		;Command
	PUSHJ P,IMPOCH
	MOVE RET,D		;Option
	PUSHJ P,IMPOCH
	PUSHJ P,IMPOUT
	SKIPN DEBUG
	JRST CILOOP
	OUTSTR [ASCIZ/ [I /]
	OUTSTR@[[ASCIZ/WILL /]
		[ASCIZ/WON'T /]
		[ASCIZ/DO /]
		[ASCIZ/DON'T /] ]-$WILL(C)
	PUSH P,D
	PUSHJ P,TYPDEC
	OUTCHR ["]"]
	JRST CILOOP

ECHOS:	ACKNEG			;He wants to echo our output. Tell him not to
	ACKNEG			;He's OK
	[ PUSHJ P,SETECHO	;He wants us to echo, that's OK
	  MOVEI C,$WILL
	  JRST SNDACK ]
	[ PUSHJ P,CLRECHO	;He wants us to stop echoing, that's OK
	  MOVEI C,$WONT
	  JRST SNDACK ]
IFN IMLSW,<
;Option table for Extended ASCII (using Imlac mode)
EXTAST:	[ SETOM IMLACT(U)	;He wants to send Extended ASCII, set Imlac mode
				;Note:  This means we have to handle line edit!
	  PTGETL B		;Turn on Imlac mode
	  TLO TAC2,IMLBIT
	  PTSETL B
	  MOVEI C,$DO		;Acknowledge it
	  JRST SNDACK ]
	[ PTGETL B		;Turn off Imlac mode
	  TLZ TAC2,IMLBIT
 	  PTSETL B
	  SETZM IMLACT(U)	;He wants to stop Extended ASCII, clear Imlac mode
	  MOVEI C,$DONT		;Acknowledge it
	  JRST SNDACK ]
	ACKNEG			;We don't send Extended-ASCII
	ACKNEG			;Never did

;Recieve Extended ASCII
DOEXTA:	PUSHJ P,IMICHW		;Get high order bits
	ASH RET,=8		;Put it into position
	MOVE C,RET		;Save somewhere safe
	PUSHJ P,IMICHW		;Get low order bits
	ADD C,RET		;Form 9 bit word
	TRNN C,600		;Any bucky bits?
	JRST NOTSP		;Yes, this one is easy
	TRNN C,177		;CALL?
	JRST [	CAIE RET,600	;Yes, handle it specially
		JRST STOP2
		JRST STOPJOB ]
	PUSH P,[BYTE (9) 240,0,0,0,0]	;Two bytes to send one IMLAC character
	DPB C,[POINT 9,(P),15]	;Set low order two bits in high order byte
	TRZ C,600
	DPB C,[POINT 9,(P),17]	;Set seven bits in second byte
DOEXT2:	PTIFRE B		;See whether there is space
	CAIL C,2		;Enough space?
	JRST [	MOVEI C,(P)		;Yes, send it!
		PTWRS9 B
		POP P,(P)		;Flush stack
		JRST IGNSUB ]		;and rest of negotiation
	PUSH P,[=15]		;Wait a little
	PUSHJ P,POSTPONE
	JRST DOEXT2		;and try again
>;IFN IMLSW

ACKPOS:	ADDI C,1		;Give positive response to request
	TRC C,2
	SOJA C,SNDACK

>;IFN NEWPRO
; Turn on echoing 
SETECHO:PTGETL B
	TLZ C,FULTWX
	PTSETL B
	POPJ P,

; Turn off echoing 
CLRECHO:PTGETL B
	TLO C,FULTWX
	PTSETL B
	POPJ P,

BEND PTYSER
SUBTTL NETOPN - Open duplex network connection
BEGIN NETOPN
;
;	PUSH P,<local socket #>
;	PUSH P,<host number (or zero for listen)>
;	PUSH P,<foreign socket number (or zero for listen)>
;	PUSH P,<byte size>
;	PUSHJ P,NETOPN
;
; Assume destroys all but U
;
↑NETOPN:HRRZI A,1(P)		;Allocate space for MTAPE block
	ADD P,[XWD 7,7]
	JUMPGE P,[PUSHJ P,DRYROT	;LOSE!!
		MOVEI A,[ASCIZ/No space for MTAPE block!
/]↔	NETKIL:	PUSHJ P,KLUSER
		JRST PREXIT ]	;Gee, this shouldn' be necessary
	MOVEI TAC,1		;Assume LISTEN then reset to CONNECT
	MOVEM TAC,(A)		;if foreign socket is defined
	MOVE TAC,-4-7(P)	;Set local socket
	MOVEM TAC,2(A)
	MOVE TAC,-3-7(P)	;Set foreign host number
	MOVEM TAC,6(A)
	SKIPN TAC,-2-7(P)	;Set foreign socket number
	JRST [	SKIPN -3-7(P)	;None, do consistency check
		JRST NETOP3
		PUSHJ P,DRYROT	;  Oops!!!!
		MOVEI A,[ASCIZ/Bad call to NETOPN
/]↔		JRST NETKIL ]	;Continue regular line
	SKIPN -3-7(P)		;Consistency check
	PUSHJ P,DRYROT		;  Failed!
	SETZM (A)		;No, want a CONNECT
NETOP3:	MOVEM TAC,5(A)
	MOVE TAC,-1-7(P)	;Set byte size
	MOVEM TAC,4(A)
	SETZM 3(A)		;Don't wait!!!
	MOVE B,[MTAPE 000,(A)]	;Now, start connect, but don't wait
	DPB U,[POINT 4,B,12]
	XCT B			;MTAPE 000,[CONNECT...]
	MOVEI TAC,77
	AND TAC,1(A)		;Pickup error code
	JUMPN TAC,[		; Connection error
	CONERR:	CAILE TAC,MAXCER	;Bless the error code returned by CONNECT
		SKIPA A,[[ASCIZ/Connect error code out of range!
/]]↔		MOVE A,CONERM(TAC)	;Get error message
		SUB P,[XWD 7,7]
		JRST NETKIL ]	;Flush MTAPE block
	CAIN U,U.ICP		;Is it the server ICP?
	  JRST CONCHK		;  Yes, just check connection now.
	MOVE RET,-1-7(P)	;Set byte size again to be safe
	MOVEM RET,4(A)
	AOS 2(A)		;Increment local socket
	SKIPE 5(A)		;Watch for listen
	SOS 5(A)		;Decrement foreign ssocket
	XCT B			;MTAPE 000,[CONNECT...]
	MOVEI TAC,77
	AND TAC,1(A)		;Pickup error code
	JUMPN TAC,CONERR	;  Error found, abort process
CONCHK:	MOVEI RET,2		;Get status of connecton
	MOVEM RET,(A)
	XCT B			;MTAPE 000,[GET STATUS]
	SETZM CHGFLG(U)		;Reset record of changes
	CAIN U,U.ICP		;Only want transmit for ICP
	  JRST SKPRCV
	MOVE TAC,2(A)		;See if recieve side is open
	TLNN TAC,RFCR
	  JRST CONWAI		;No, wait for it
SKPRCV:	MOVE TAC,1(A)		;See if transmit side is open
	TLNN TAC,RFCR
	  JRST CONWAI		;No, wait for it too
	SUB P,[XWD 7+5,7+5]	;Flush stack
	JRST @5(P)		;Done, return

;Wait for something to happen
CONWAI:	TURNOFF [INTIMS]	;Watch timing race
	SKIPE CHGFLG(U)		;Did something change?
	JRST [	TURNON [INTIMS]		;Enable and try again
		JRST CONCHK ]
;**** Crock: TURNON [INTIMS] in  WSCHED !!!! ****
	PUSHJ P,GETPRO		;Set code to wait on
	HRLM U,%DATUM(TAC)
	PUSH P,[IMSQUE]		;IMP Status wait
	PUSHJ P,WSCHED		;Wait for connection
	JRST CONCHK
BEND NETOPN
↑CONERM:[ASCIZ/No error, how did we get here.
/]↔	[ASCIZ/Socket in use.
/]↔	[ASCIZ/Can't change socket numbers.
/]↔	[ASCIZ/Horrible system error - CONNECT.
/]↔	[ASCIZ/No link available. IMP capacity exceeded.
/]↔	[ASCIZ/Illegal byte size.
/]↔	[ASCIZ/IMP dead.
/]↔	[ASCIZ/Gender mismatch
/]
	MAXCER←←.-CONERM-1
SUBTTL SPY    - Find out who is using NETGRF
BEGIN SPY

↑SPY:	SKIPE TAC,WHOTAB(U)	;Do we know yet?
	MOVE TAC,['100100']	;Check 100,100 everytime
	CAME TAC,WHOTAB(U)
	POPJ P,
	SKIPN TAC,PTYNUM(U)	;Get corresponding PTY, if any
	POPJ P,
	HRRZ TAC,TAC
	TTYJOB TAC,		;Get job number if any
	JUMPE TAC,CPOPJ
	MOVEI TAC2,211		;Access system's PRJPRG table
	PEEK TAC2,
	ADD TAC2,TAC
	PEEK TAC2,
	JUMPE TAC2,CPOPJ	;If zero, remember most recent
	CAMN TAC2,[' 1  2']	;If it's accounting, forget it!
	POPJ P,
	PUSH P,WHOTAB(U)	;Save old entry for later testing
	MOVEM TAC2,WHOTAB(U)	;Set entry in WHOTAB
	SETZM SPYBUF
	CAME TAC2,['100100']
	JRST NOT100
	HRRM TAC,SPYTMC
	MOVE TAC2,[XWD 1,SPYTMC]
	TMPCRD TAC2,
	JRST NOT100
	MOVE TAC2,SPYBUF
	EXCH TAC2,SPYTAB(U)
	CAME TAC2,SPYTAB(U)
	JRST GOTNET
NOT100:	MOVE TAC2,WHOTAB(U)
	CAMN TAC2,(P)
	JRST SPYDON
GOTNET:	PUSHJ P,OUTWHR
	MOVE TAC2,WHOTAB(U)
	PUSHJ P,LOGIT
	XWD 7,[ASCIZ/User #/]
	XWD =10,U
	XWD 7,[ASCIZ/ is job /]
	XWD =10,TAC
	XWD 7,[ASCIZ/ /]
	XWD 6,TAC2
	XWD 7,[ASCIZ/ /]
	XWD 7,SPYBUF
	0
SPYDON:	POP P,(P)
CPOPJ:	POPJ P,
BEND SPY
SUBTTL GRISER - Graphic Input Service
IFN GRFPRO,<
BEGIN GRISER
;
; Handle input from the network graphics connection
↑GRISER:MOVEI A,3		;Pointer into INHDR
	IMULM U,A
	PUSHJ P,IMICHS		;Get command
	JRST [	SETZM PTIFUL(U)		;Nothing to do, quit
		TURNON [INTINP]
		POPJ P,]
	CAIN RET,$INQRS		;Better be an inquiry response
	JRST INQRS
	PUSH P,[[ASCIZ/Unknown graphics input.
//]]↔
	PUSHJ P,GRFERM
	skipe debug
	outchr ret
	JRST GRISER
SUBTTL RDINQRS- Read Inquiry Response
;
INQRS:	PUSHJ P,RCVCNT		;Receive the count
	MOVE D,RET		;Save count
IQLOOP:	SOJL D,INQDON
	PUSHJ P,IMICHW		;Get an option
	MOVE C,RET		;Save it
	PUSHJ P,RCVCNT		;Get the count
	MOVE B,RET
	CAIG C,MAXOP		;Bigger than we know about?
	JRST @OPTTAB(C)		;No, service it then
IGNOPT:	SOJL B,IQLOOP		;Ignore this option
	PUSHJ P,IMICHW
	JRST IGNOPT
OPTTAB:	IGNOPT			;Unknown, oh, well, ignore it for now
	IIMPL			;$IIMPL - Implemented commands
	ISCREEN			;$ISCRE - Screen coordinate system
MAXOP←←.-OPTTAB

;Implemented commands
IIMPL:	MOVEI C,=8		;Point to table of implemented commands
	IMULM U,C
	ADDI C,IMPLTB-8*NUSERS
	HRLI C,(<POINT 8,0>)
	SETZM (C)		;Zero the table
	MOVEI RET,1(C)
	HRL RET,C
	BLT RET,7(C)
	JUMPE B,[PUSH P,[[ASCIZ/No implemented commands recieved??
/]]↔		 PUSHJ P,GRFERM
		 JRST IQLOOP]
IILOOP:	PUSHJ P,IMICHW		;Start filling the table
	IDPB RET,C
	SOJG B,IILOOP
	JRST IQLOOP
;Screen coordinate system
ISCREEN:PUSHJ P,RCV32
	MOVEM RET,XMIN-NUSERS(U)	;X minimum
	PUSHJ P,RCV32
	MOVEM RET,YMIN-NUSERS(U)	;Y minimum
	PUSHJ P,RCV32
	SUB RET,XMIN-NUSERS(U)		;X multiplier
	ASH RET,-1			;X constant used in SNDCOORD
	MOVEM RET,XMUL-NUSERS(U)
	ADD RET,XMIN-NUSERS(U)
	MOVEM RET,XK-NUSERS(U)
	PUSHJ P,RCV32
	SUB RET,YMIN-NUSERS(U)		;Y multiplier
	ASH RET,-1			;Y constant used in SNDCOORD
	MOVEM RET,YMUL-NUSERS(U)
	ADD RET,YMIN-NUSERS(U)
	MOVEM RET,YK-NUSERS(U)
	PUSHJ P,RCVCNT
	MOVEM RET,BYTES-NUSERS(U)
	CAILE RET,4
	JRST [	PUSH P,[[ASCIZ/Max. bytes for co-ordinates is 4/]]
		PUSHJ P,GRFERM
		JRST IQLOOP ]
	MOVNI B,8
	IMULM RET,B
	HRRZM B,ROTS-NUSERS(U)
	JRST IQLOOP
	
;Inquiry done, remember this and send greeting 
INQDON:	MOVBI RET,INQBIT
	ORM RET,FLAGS-NUSERS(U)
	MOVBI RET,GREBIT
	ENTERLOCK DPYLOK
	TDZ RET,FLAGS-NUSERS(U)
	ORM RET,FLAGS-NUSERS(U)
	LEAVELOCK DPYLOK
	JUMPE RET,GRISER
	PUSH P,[SNDGREET]
	PUSHJ P,SCHED
	JRST GRISER
	
BEND GRISER
>;IFN GRFPRO
SUBTTL RCVCNT - Recieve Graphics Count
IFN GRFPRO,<
BEGIN RCVCNT
↑RCVCNT:PUSHJ P,IMICHW		;Get first byte
	CAIGE RET,200		;Small number?
	POPJ P,			;Yes, return
	SUBI RET,200		;This is the high order part
	LSH RET,8
	PUSH P,RET
	PUSHJ P,IMICHW		;Get low order part
	ADD RET,(P)		;Now we have the whole thing
	SUB P,[XWD 2,2]
	JRST @1(P)
BEND RCVCNT
>;IFN GRFPRO
SUBTTL RCV32  - Receive 32 bits
IFN GRFPRO,<
BEGIN RCV32
;
; Called with:
;	MOVEI U,<user number>
;	MOVEI A,<3*user number>
;	PUSHJ P,RCV32
; Returns 32 bit number in RET
; Preserves all other ACs
;
↑RCV32:	PUSH P,B
	PUSHJ P,IMICHW
	MOVE B,RET
	TRNE B,200
	ORI B,777400
	LSH B,8
	PUSHJ P,IMICHW
	ADD B,RET
	LSH B,8
	PUSHJ P,IMICHW
	ADD B,RET
	LSH B,8
	PUSHJ P,IMICHW
	ADD RET,B
	POP P,B
	POPJ P,
BEND RCV32
>;IFN GRFPRO
SUBTTL GRFERM - Graphics error message
IFN GRFPRO,<
BEGIN GRFERM
↑GRFERM:SKIPE DEBUG		;Send an error message
	OUTSTR @-1(P)
	SUBI U,NUSERS
	PUSH P,-1(P)
	PUSHJ P,IMPSTR
	ADDI U,NUSERS
	SUB P,[XWD 2,2]
	JRST @2(P)
BEND GRFERM
>;IFN GRFPRO
SUBTTL IIISIM - Simulate III Display
IFN GRFPRO,<
BEGIN IIISIM
;
; Called with:
;	PUSH P,[<job number>]
;	PUSH P,[<display header>]
;	PUSH P,[<POG number>]
;	PUSHJ P,IIISIM
;
; It is assumed that all ACs are destroyed except 0, U and P.
;
IIIX←D+1		;III X Register
IIIY←IIIX+1		;III Y Register
IIIPC←IIIY+1		;III Program Counter
IIIBEG←IIIPC+1		;Beginning of display program
IIIEND←IIIBEG+1		;End of display program + 1
;The following three must be in order
IIIMIN←IIIEND+1		;Minimum address in core
IIIMAX←IIIMIN+1		;Maximum address in core
RELOC←IIIMAX+1		;Address of III program buffer - IIIMIN
IFGE RELOC-U,<PRINTX Too many AC's in IIISIM>
;
WRAPBIT←←20
EDGEBIT←←40
;
↑IIISIM:;ADDI U,NUSERS		;We're outputting on graphics link! *** Should already have set U
	MOVEI A,3		;Pointer to output buffer
	IMULM U,A
	SETO TAC,		;Display channel already in use?
	EXCH TAC,DPYUSE-NUSERS(U)
	JUMPE TAC,IIISI2	;No, continue then
	PUSH P,[GRFQUE]		;Else wait for it
	PUSHJ P,WSCHED
IIISI2:	MOVEI RET,$SGOPN	;Open segment:
	PUSHJ P,IMPOCHR		;$SGOPN <segment number>
	MOVE RET,-1(P)
	PUSHJ P,SNDNAM
	MOVE B,-3(P)		;Read the display header
	MOVE C,-2(P)
	CAME B,THISJOB		;Is it us?
	SKIPN B			;or zero
	JRST [	MOVE IIIBEG,(C)		;No moniter call necessary!
		MOVE IIIEND,1(C)
		SETZ RELOC,
		MOVE IIIMIN,IIIBEG
		MOVE IIIMAX,IIIEND
		JRST GOTHDR ]
	HRLI C,-2		;Setup for JOBRD, number of words to transfer
	MOVEI D,IIIBEG
	PUSHJ P,GET1K		;Get a block to put display code into
	MOVE RELOC,RET
	SETZB IIIMIN,IIIMAX	;Nothing in core yet
	MOVEI RET,B
	JOBRD RET,
	JRST [JRDERR:	MOVEI RET,[ASCIZ/Can't read display buffer of user job/]
			JRST IIIERR ]
GOTHDR:	ADD IIIEND,IIIBEG
	ADD IIIMAX,IIIMIN
	MOVEI IIIPC,1(IIIBEG)
	HRLI IIIBEG,RELOC	;Put in index field for RELCHK
	HRLI IIIEND,RELOC
	HRLI IIIMIN,RELOC
	HRLI IIIMAX,RELOC
	HRLI IIIPC,RELOC
RELOOP:	MOVEI RET,=100		;Interval between rescheduling
	MOVEM RET,-2(P)
IILOOP:	SOSG -2(P)		;Time to reschedule?
	JRST [	PUSH P,[RUNQUE]	;Yes, let others run!
		PUSHJ P,RESCHED
		JRST RELOOP ]
	PUSHJ P,RELCHK		;Check relocation
	MOVE RET,@IIIPC		;Get instruction
	TRZE RET,1		;Text?
	JRST DOTEXT		;Yes, quick dispatch
	MOVE B,RET		;Get opcode
	ANDI B,17		;Low order 4 bytes
	LSH B,-1		;We already know Bit 35 = 0
	JOV @IIIOPS(B)		;Turn off overflow bit
	JRST @IIIOPS(B)		;[Overflow not on]
IIIOPS:	DOJUMP	;HALT,JMP	- Interpet JMP, treat HALT as EOF
	DOSVW	;SVW		- Short Vector Word
	IINOP	;JMS,JSR,SAVE	- [Ignore, readonly]
	DOLVW	;LVW		- Long Vector Word
	IINOP	;SEL		- Select [Ignore, doesn't apply]
	DOTSS	;TSS		- Test flags
	IINOP	;REST		- Restore [Ignore, since SAVE doesn't work]
	IINOP	;???
; No op
IINOP:	AOJA IIIPC,IILOOP
; Interpet text
DOTEXT:	JUMPE RET,IINOP		;A word of nulls is a nop
	MOVEI RET,$SGTXT	;Emit begin segment text
	PUSHJ P,IMPOCHR
	MOVEI B,1		;Count number of words of text (min = 1)
	MOVE C,IIIPC		;Save address of instruction
DOTEX1:	ADDI IIIPC,1		;Look forward for more text
	CAMN IIIPC,IIIEND	;Watch for end (we don't want RELCHK to see it)
	JRST DOTEX2		;Found!
	PUSHJ P,RELCHK		;Get word into core
	MOVE RET,@IIIPC		;Another text word?
	CAIN RET,1		;Ignore text with just 1
	JRST DOTEX2
	TRNE RET,1
	AOJA B,DOTEX1		;;Yes, look for more, counting as we go
DOTEX2:	MOVE IIIPC,C		;Get back to beginning of text
	MOVEI RET,5		;Calculate number of bytes
	IMULM B,RET
	PUSHJ P,SNDCNT		;Put out graphic style count
DOTEX3:	MOVE C,[POINT 7,@IIIPC]	;Get ready to put out text
	PUSHJ P,RELCHK		;Get word into core
DOTEX4:	ILDB RET,C		;Get a character from III program
	PUSHJ P,IMPOCHR		;Send it out
	CAME C,[POINT 7,@IIIPC,34]	;Last byte in word?
	JRST DOTEX4		;No, send some more characters
	ADDI IIIPC,1		;Look at next word
	SOJG B,DOTEX3		;But only if we know it's text
	JRST IILOOP		;Done with string of text

; Interpet JMP and HALT
DOJUMP:	TRNN RET,20		;JMP?
	JRST IIDONE		;No, HALT.  We're done
	HLR IIIPC,RET		;Yes, set PC
	JRST IILOOP

; Interpet Long Vector Word
DOLVW:	MOVE B,RET		;Copy instruction
	MOVE C,RET
	AND B,[777600,,]	;Get X coordinate
	ROT C,=11		;Get Y coordinate
	AND C,[777600,,]
	ROT RET,-4		;Get vector type
	ANDI RET,7
	TRZN RET,4		;Relative vector?
	JRST [	ADDB B,IIIX		;Do arithmetic here
		ADDB C,IIIY
		JOV [	MOVEI RET,WRAPBIT	;Watch for wraparound!
			ORM IIFLAG-NUSERS(U)
			JRST DOLVW2 ]
		JRST DOLVW2 ]
	MOVEM B,IIIX		;New X & Y
	MOVEM C,IIIY
DOLVW2:	MOVE RET,VECTYP(RET)	;Emit appropriate vector opcode
	PUSHJ P,IMPOCHR
	PUSHJ P,EDGTST		;Check for edge overflow and send coordinates
	AOJA IIIPC,IILOOP	;Next instruction

; Interpet Short Vector Word
DOSVW:	PUSHJ P,DOSVW2		;Do first vector
	MOVE RET,@IIIPC
	LSH RET,=16
	PUSHJ P,DOSVW2		;Do second vector
	AOJA IIIPC,IILOOP	;Next instruction
; Interpet half of Short Vector Word
DOSVW2:	MOVE B,RET		;Copy instruction
	ASH B,-4		;Get X coordinate
	AND B,[777600,,0]
	MOVE C,RET
	LSH C,7			;Get Y coordinate
	ASH C,-4
	AND C,[777600,,0]
	ADDB B,IIIX		;Short vectors are always relative
	ADDB C,IIIY
	JOV [	MOVEI RET,WRAPBIT		;Watch for wraparound!
		ORM RET,IIFLAG-NUSERS(U)	;Turn on bits in flag word
		JRST .+1 ]
	LDB RET,[POINT 2,RET,15]	;Get vector type
	MOVE RET,VECTYP(RET)
	PUSHJ P,IMPOCHR		;Send NGP command
; Fall thru into EDGTST
; Send vector coordinates
EDGTST:	MOVM RET,B		;Check for edge overflow
	TLNE RET,200000
	JRST EDGTS2
	MOVM RET,C		;Both X and Y
	TLNN RET,200000
	JRST EDGTS3		;Both OK, send coordinates
EDGTS2:	MOVEI RET,EDGEBIT
	ORM RET,IIFLAG-NUSERS(U)	;Turn on bits in flag word
EDGTS3:	LSH B,1			;For now, just wraparound on overflow!
	LSH C,1
	JRST SNDCOORD

; Mapping of III vector types onto NGP opcodes
VECTYP:	$SGDRW		;Visible
	$SGDOT		;Dot
	$SGMOV		;Invisible
	$SGDOT		;Undefined, currently dot on III

; Interpete TSS instruction
DOTSS:	LDB B,[POINT 8,RET,23]	;Do test first
	TDNN B,IIFLAG-NUSERS(U)	;Skip if any bits set
	TRNN RET,20		;Invert skip perhaps
	ADDI IIIPC,1
	LDB B,[POINT 8,RET,7]	;Clear bits
	LDB C,[POINT 8,RET,15]	;Set bits
	MOVE RET,A		;If both on, compliment!
	AND RET,B
	ANDCM B,RET		;Turn off complimented bits
	ANDCM C,RET
	ANDCAM B,IIFLAG-NUSERS(U)	;Do clears
	IORM C,IIFLAG-NUSERS(U)		;Do sets
	XORM RET,IIFLAG-NUSERS(U)	;Do complimenting
	AOJA IIIPC,IILOOP

;Nothing can be on stack when RELCHK is called!!!
RELCHK:	CAMLE IIIPC,IIIMIN	;Check to see if in core
	CAML IIIPC,IIIMAX
	JRST NOTIN		;Not in core, check address
	POPJ P,			;OK, return
NOTIN:	CAMLE IIIPC,IIIBEG	;Within display buffer?
	CAML IIIPC,IIIEND
	JRST [	CAME IIIPC,IIIMAX	;No. Check to for running off end
		JRST ADRCHK 		;No! Address out of bounds
		POP P,(P)		;Flush RELCHK's return addess
		JRST IIDONE ]		;Assume he forgot HALT
	ADDI RELOC,(IIIMIN)	;Set up my address of block
	MOVE IIIMIN,IIIPC	;Calculate  - number of words to read
	SUB IIIMIN,IIIEND
	CAMGE IIIMIN,[-=1024]	;Too many?
	MOVNI IIIMIN,=1024	;YES, USE BLOCK SIZE
	MOVEI IIIMAX,(IIIPC)	;Set up -<word count>,<users address>
	HRL IIIMAX,IIIMIN
	MOVE IIIMIN,-4(P)	;GET JOB NUMBER TO READ FROM
	MOVEI RET,IIIMIN
	JOBRD RET,
	JRST JRDER2		;Can't read it!!!
	HLRO IIIMAX,IIIMAX	;Setup IIIMAX, IIIMIN, RELOC
	SUBM IIIPC,IIIMAX
	MOVE IIIMIN,IIIPC
	SUBI RELOC,(IIIMIN)
	POPJ P,			;Return

JRDER2:	SETZ IIIMIN,		;It'll be wrong since RELOC=base of buffer
	SKIPA RET,[[ASCIZ/Can't read display buffer of user job/]]
ADRCHK:	MOVEI RET,[ASCIZ/Display instruction out of bounds/]
	POP P,(P)		;Flush return address from RELCHK
IIIERR:	PUSH P,RET		;Argument for IMPSTR
;	PUSHJ P,IMPOUT		;Flush Graphics channel first
;	SUBI U,NUSERS		;Use TELNET stream
;	SUBI A,3*NUSERS
	PUSH P,[[ASCIZ/
*** /]]
	PUSHJ P,GRFERM
	PUSHJ P,GRFERM
	PUSH P,[[ASCIZ/ - IIISIM ***
/]]↔	PUSHJ P,GRFERM
;	ADDI U,NUSERS
IIDONE:	MOVEI RET,$SGCLS	;Close segment
	PUSHJ P,IMPOCHR
	MOVEI RET,$SGPOS	;Post it so it will be seen
	PUSHJ P,IMPOCHR
	MOVE RET,-1(P)
	PUSHJ P,SNDNAM
	MOVEI RET,$ENDUP	;Force up date so it will be seen now
	PUSHJ P,IMPOCHR
	PUSHJ P,IMPOUT
	SKIPN RELOC
	JRST QRET
	ADDI RELOC,(IIIMIN)	;Get address of free storage block
	PUSH P,RELOC
	PUSHJ P,REL1K
QRET:;	POP P,(P)
;	POPJ P,
	ENTERLOCK DPYLOK	;Check for someone else waiting
	PUSH P,U
	PUSH P,[GRFQUE]
	PUSHJ P,SRHQUE
	JUMPN RET,[ PUSH P,RET	;Yes, schedule to run
		PUSH P,[PRIQUE]
		PUSHJ P,ENQUE
		AOS RUNWAIT	;Another process waiting for service
		JRST NOTWAI ]
	SETZM DPYUSE-NUSERS(U)		;Release display channel
NOTWAI:	LEAVELOCK DPYLOK
	SUB P,[XWD 4,4]
	JRST @4(P)
BEND IIISIM
>;IFN GRFPRO
SUBTTL SNDCOOR- Send coordinates
IFN GRFPRO,<
BEGIN SNDCOORD
;
; Called with:
;	MOVE B,[<X>]		;Where X is from -377777777777 to 377777777777
;	MOVE C,[<Y>]
;	PUSHJ P,SNDCOORD
; Destorys RET,B,C
;
↑SNDCOORD:
	PUSH P,D		;Save an AC
	MUL C,YMUL-NUSERS(U)	;Result into C, clobbering D
	ADD C,YK-NUSERS(U)
	MOVE D,C		;Save result
	MUL B,XMUL-NUSERS(U)	;Result into B, clobeering D
	ADD B,XK-NUSERS(U)
	MOVE RET,B		;Do X first
	PUSHJ P,SNDONE
	MOVE RET,D		;Then Y
	PUSHJ P,SNDONE
	POP P,D			;Restore D
	POPJ P,
;Send one coordinate
SNDONE:	ROT RET,@ROTS-NUSERS(U)	;Amount to rotate to position high order byte
	MOVE B,BYTES-NUSERS(U)	;Number of bytes
COLOOP:	ROT RET,8		;Get next byte
	PUSHJ P,IMPOCHR		;Output a byte
	SOJG B,COLOOP		;For each byte
	POPJ P,

BEND SNDCOORD
>;IFN GRFPRO
SUBTTL SNDCNT - Send count (for NGP)
IFN GRFPRO,<
BEGIN SNDCNT
;
; Called with:
;	MOVE RET,[<count>]
;	PUSHJ P,SNDCNT
; Destroys RET
;
↑SNDCNT:CAIGE RET,200		;Fit in one byte?
	JRST IMPOCHR		;Sure, this is easy
	ROT RET,-8		;No, output higher order bits
	ADDI RET,200		;Plus
	PUSHJ P,IMPOCHR
	ANDCMI RET,377		;Turn off higher order bits
	ROT RET,8		;Get back low order bits
	JRST IMPOCHR		;And output them too
BEND SNDCNT
>;IFN GRFPRO
SUBTTL STRLEN - Length of ASCIZ string
IFN GRFPRO,<
BEGIN STRLEN
;
; Called with:
;	PUSH P,[[ASCIZ/<string>/]]
;	PUSHJ P,STRLEN
; Destroys RET
;
↑STRLEN:PUSH P,TAC		;Save an AC
	SETZ RET,		;Init count
SNDLE1:	ILDB TAC,-1(P)		;Send each character
	JUMPE TAC,[POP P,TAC	;  Until NULL is found
		SUB P,[XWD 2,2]
		JRST 2(P)]
	AOJA RET,SNDLE1
BEND STRLEN
>;IFN GRFPRO
SUBTTL SNDSTR - Send string (for NGP)
IFN GRFPRO,<
BEGIN SNDSTR
;
; Called with:
;	PUSH P,[[ASCIZ/<string>/]]
;	PUSHJ P,SNDSTR
; Destroys RET
;
↑SNDSTR:
	HLLZ RET,-1(P)		;Pick up byte part of string pointer
	JUMPN RET,SNDST1	;  It's good, use it!
	MOVSI RET,(<POINT 7,0>)	;Make into string pointer
	HLLM RET,-1(P)
SNDST1:	PUSH P,-1(P)		;Get length of string
	PUSHJ P,STRLEN
	PUSHJ P,SNDCNT		;Graphics format begins with string length
SNDST2:	ILDB RET,-1(P)		;Send each character
	JUMPE RET,[SUB P,[XWD 2,2]	;Until NULL is found
		JRST 2(P)]
	PUSHJ P,IMPOCHR
	JRST SNDST2
BEND SNDSTR
SUBTTL SNDNAM - Send segment name (for NGP)
;
; Called with:
;	MOVEI RET,<segment number>
;	PUSHJ P,SNDNAM
;
SNDNAM:	ROT RET,-8
	PUSHJ P,IMPOCHR	;Send high order 8 bits
	ROT RET,8
	JRST IMPOCHR		;Send low order 8 bits
>;IFN GRFPRO
;-------------------------------------------------
SUBTTL GET1K  - Get a 1024 word block
BEGIN GET1K
;
; Called with:
;	PUSHJ P,GET1K
; Return address of block in RET
;
↑GET1K:	ENTERLOCK .1KLOK	;Interlock against modifying 1K free list
	SKIPN RET,FREE1K	;Any blocks handy?
	JRST GETCOR		;No, get one from system
	PUSH P,RET		;Yes, save its address
	HRRZ RET,(RET)		;And get next free one if any
	MOVEM RET,FREE1K	;for the next time thru
RETURN:	POP P,RET		;Get back block to return
	LEAVELOCK .1KLOK	;Interlock against modifying 1K free list
	POPJ P,			;and return
GETCOR:	PUSHJ P,USERMODE	;Make sure we're at user level!!!
	MOVE RET,JOBREL↑
	ADDI RET,1		;Address of new block
	PUSH P,RET
;	ORI RET,=1023		;System will do this, included for clarity
	CORE RET,
	PUSHJ P,DRYROT		;Lose big!!!
	JRST RETURN
BEND GET1K
SUBTTL REL1K  - Release a 1024 word block
BEGIN REL1K
; Called with:
;	PUSH P,[<block address>]
;	PUSHJ P,REL1K
; Preserves all ACS
;
↑REL1K:	ENTERLOCK .1KLOK	;Interlock against modifying 1K free list
	EXCH TAC,-1(P)		;Get address of block and save AC
	MOVEI TAC,=1024-1(TAC)	;Top of core perhaps
	CAME TAC,JOBREL↑
	JRST NOTTOP		;Didn't think so
	SUBI TAC,=1024		;Yes, let's core down then
	CORE TAC,
	PUSHJ P,DRYROT		;Should NEVER lose
	JRST RETURN
NOTTOP:	SUBI TAC,=1024-1	;Restore pointer to block
	EXCH TAC,FREE1K		;Get old head and save new
	MOVEM TAC,@FREE1K	;Point new at old
RETURN:
	LEAVELOCK .1KLOK	;Interlock against modifying 1K free list
	MOVE TAC,-1(P)		;Restore AC
	SUB P,[XWD 2,2]
	JRST @2(P)		;And return
	
BEND REL1K
SUBTTL MKPBLK - Make a Process Block
BEGIN MKPBLK
;
;	PUSHJ P,MKPBLK
; Returns address of process in RET
; Destroys TAC, TAC2
;
↑MKPBLK:
;Turn off interrupts as following is not reentrant
	PUSH P,A
	ENTERLOCK PBKLOK	;Interlock against modifying process block free list
GOTBLK:	SKIPN RET,PROCFR	;Get a procedure block
	JRST NOPROC		;None left!!!
	MOVE TAC,%LINK(RET)	;Make point to free next procedure
	HLRZM TAC,PROCFR
;Restore interrupts now
	LEAVELOCK PBKLOK	;Interlock against modifying process block free list
	POP P,A
	POPJ P,			;And return

NOPROC:	SKIPN INTLEV		;At interrupt level
	JRST NOTINT
	PUSHJ P,USERMODE	;Enter user mode
	MOVE A,LOKMSK		;*** KLUDGE ***
	SETZM LOKMSK
	MOVEM A,OLDMSK
NOTINT:	PUSHJ P,GET1K		;Get a 1K block
	MOVEI TAC,=1024-1(RET)	;End of block
	MOVEM TAC,BLKEND
	MOVE TAC,RET
	MOVSI TAC2,-%PDLSZ
	ADDI TAC,PROCSZ-1	;Point to end of block
NWLOOP:	HRRI TAC2,-PROCSZ(TAC)	;Make an IOWD
	MOVEM TAC2,%PDLIO(TAC)	;Now, set up PDL pointer
	ADDI TAC,PROCSZ		;Point to end of block
	CAMG TAC,BLKEND		;Past last?
	JRST [	HRLZM TAC,%LINK-PROCSZ(TAC)	;And pointer to next block
		JRST NWLOOP ]
	SETZM %LINK-PROCSZ(TAC)	;Last pointer out of bounds, make end mark
	ADDI RET,PROCSZ-1	;Now, make pointer to first one
	MOVEM RET,PROCFR
	JRST GOTBLK		;This can have the timing loss of invoking processes
				;in the wrong order from interrupt level.
ARRAY ACSAVE[20]
INTEGER BLKEND

BEND MKPBLK
SUBTTL MKPROC - Make a Process
BEGIN MKPROC
;
;	PUSH P,<PC of process on stack>
;	PUSHJ P,MKPROC
; Returns address of process in RET
; Destroys TAC, TAC2
; Calls MKPBLK
;
↑MKPROC:
;Turn off interrupts as following is not reentrant
	PUSHJ P,MKPBLK		;Get a process block
	HRRZM U,%USER(RET)	;Owning User number
	AOS %USER(RET)		;		    + 1
	HRLM U,%USER(RET)	;Set Datum to user number as default
	MOVE TAC,%PDLIO(RET)	;Set up enviroment
	EXCH TAC,P		;Use new stack
	PUSH P,[PREXIT]		;So process exits with a POPJ
	PUSH P,-1(TAC)		;PC for process
	exch tac2,(p)
	hrrz tac2,tac2
	cail tac2,$bgnet
	caile tac2,endcod
	jrst [	exch tac2,(p)
		outstr[asciz/Attempt to create process with PC out of bounds!/]
		pushj p,dryrot
		movei tac2,prexit
		jrst .+1]
	exch tac2,(p)
	PUSHACS
	SUBI P,17
	HRROM P,%PACS(RET)	;Copy of P (also obliterate old free storage pointer
				;so if a routine tries to reference it uninitialized,
				;it will get an ILL MEM REG).
	MOVE P,TAC		;Get back old stack
	POP P,-1(P)		;Flush arg from stack
	POPJ P,			;And return
BEND MKPROC
SUBTTL KLPROC - Kill a Process or Process Block
BEGIN KLPROC
;
; Process pointer on stack
; Destroys TAC, TAC2
;
↑KLPBLK:
↑KLPROC:
	ENTERLOCK PBKLOK	;Interlock against modifying process block free list
	HRRZ TAC,-1(P)		;Pick up process header.
	CAML TAC,OLDFF		;Address check it
	CAMLE TAC,JOBREL↑
;**** This is where alot of errors happen! ****
	PUSHJ P,DRYROT
;	SKIPGE %PDLIO(TAC)	;More checking, that should be an IOWD
;	SKIPG %LINK(TAC)	;That should be an queue pointer, hence positive
;	PUSHJ P,DRYROT
	EXCH TAC,PROCFR		;New free block
	MOVSM TAC,@PROCFR	;Link to next free block
	LEAVELOCK PBKLOK	;Interlock against modifying process block free list
	POP P,-1(P)		;Flush arg
	POPJ P,			;And return
BEND KLPROC
SUBTTL ENQUE  - Enter into Queue
BEGIN ENQUE
;
;	PUSH P,<process pointer>
;	PUSH P,<queue>
;	PUSHJ P,ENQUE
;
; Destroys TAC, TAC2
;
↑ENQUE:
	ENTERLOCK QUELOK	;Interlock against queue modification
;Stack: <process pointer>,<queue>,<return address>
	MOVE TAC,-2(P)		;Get process pointer
	MOVE TAC2,-1(P)		;And queue block
	HRRZS %LINK(TAC)	;Mark end of queue
	SKIPN (TAC2)		;Empty queue?
	JRST [	MOVEM TAC,(TAC2)	;Yes, make it head also as well as tail
		JRST RETURN ]
	HRLM TAC,@1(TAC2)	;Store pointer to next last queue element
RETURN:	MOVEM TAC,1(TAC2)	;Move into end of queue marker
	AOS 2(TAC2)		;Increment number in queue
	LEAVELOCK QUELOK	;Interlock against queue modification
	SUB P,[XWD 3,3]		;Flush stack
	JRST @3(P)		;And return
BEND ENQUE
COMMENT ⊗ NOT USED
---------------------------------------------------------------------
SUBTTL ENHQUE - Enter into Head of Queue
BEGIN ENHQUE
;
;	PUSH P,<process pointer>
;	PUSH P,<queue>
;	PUSHJ P,ENTQUE
;
; Destroys TAC, TAC2
;
↑ENHQUE:PUSH P,[-1]		;Turn off interrupts as following is not reentrant
;	IMSKCR (P)
	ENTERLOCK QUELOK	;Interlock against queue modification
;Stack: <process pointer>,<queue>,<return address>,<interrupt mask>
	MOVE TAC,-3(P)		;Get process pointer
	MOVE TAC2,@-2(P)	;Point it to new element at old one (if any)
	HRLM TAC2,%LINK(TAC)
	MOVEM TAC,@-2(P)	;New head of queue
	JUMPN TAC2,NOTEMP	;Was it empty before?
	MOVE TAC2,@-2(P)
	MOVEM TAC,1(TAC2)	;New tail too.
NOTEMP:	MOVE TAC2,@-2(P)
	AOS 2(TAC2)		;Increment number in queue
;	INTMSK 1,(P)		;Restore interrupts
	LEAVELOCK QUELOK	;Interlock against queue modification
	SUB P,[XWD 4,4]		;Flush stack
	JRST @3(P)		;And return
BEND ENHQUE
---------------------------------------------------------------------
⊗;
SUBTTL ENTCLK - Enter into Clock Queue
BEGIN ENTCLK
;
;	PUSH P,<process pointer>
;	PUSH P,<number of tics to delay>	;(up to 2↑18)
;	PUSHJ P,ENTCLK
;
; Destroys TAC, TAC2
;
; This routine enters a process into the Clock Queue (CLKQUE) so that it will
; be run at that time.  It looks at the front of the queue and inserts it in
; front if it will happen before the next scheduled interrupt.  Otherwise, it
; looks down the queue, subtracting the time increment of each entry, and until
; an entry is found which will occur after that process.
;
↑ENTCLK:PUSH P,A		;Save an AC
	ENTERLOCK QUELOK	;Interlock against queue modification
;Stack: <process pointer>,<number of tics>,<return address>,<old A>
;		-3		-2	          -1		0
	SKIPE TAC,-2(P)		;Bless the time increment
	TLNE TAC,777777
	PUSHJ P,DRYROT
	TIMER A,		;Pick up current time (tics past midnight)
	ADDB TAC,A		;Add current time to find time of new interrupt
	SKIPN TAC2,NXTTIM	;Anything queued?
	JRST EMPTY		;And put in front of queue
	SUBM TAC,TAC2		;Difference between new and old interrupts
	JUMPL TAC2,BEFORE	;New once occurs before first in queue
	CAMG TAC2,[DAYTIC/2]	;Did we pass midnight
	JRST AFTER		;No, happens sometime after first entry in queue
	ADD TAC2,[DAYTIC]	;Yes, figure time as on a two day basis
	JUMPL TAC2,BEFORE	;Gee, it's before first time queue after all
AFTER:	HRLO TAC2,TAC2		;Move into left halt.  777777 in the right half
				;guarantees that when the other half of the datum
				;word is subracted, it will not carry into left half.
	SKIPA A,CLKQUE		;Pick up head of the clock queue
AFLOOP:	MOVE A,TAC		;Remember address of previous node for insertion
	HLRZ TAC,(A)		;Get next node
	JUMPE TAC,ATEND		;No more, insert at end of queue
	SUB TAC2,%DATUM(TAC)	;Insert here?
	JUMPGE TAC2,AFLOOP	;No, later than that
	ADD TAC2,%DATUM(TAC)	;Yes! Compensate of over-subtraction (a la division).
	HLLZ TAC2,TAC2		;Flush garbage from right half of datum word
	MOVN TAC2,TAC2		;Update time increment for next node
	ADDM TAC2,%DATUM(TAC)
	MOVN TAC2,TAC2
ATEND:	HRLM TAC,@-3(P)		;Point current node at next node
	MOVE TAC,-3(p)
	HRLM TAC,%LINK(A)	;And previous node at current node
	HLLM TAC2,%DATUM(TAC)	;Set time increment from previous interrupt
RETURN:	AOS CLKQUE+2		;Increment number of entries in the queue
	LEAVELOCK QUELOK	;Interlock against queue modification
	POP P,A			;Restore A
	SUB P,[XWD 3,3]		;Flush garbage on stack
	JRST @3(P)
BEFORE:	MOVN TAC,TAC2		;Set increment for old head of queue
	MOVE TAC2,CLKQUE
	HRLM TAC,%DATUM(TAC2)
EMPTY:	MOVE TAC,-3(P)		;Point current node at old head of queue
	HRLM TAC2,%LINK(TAC)
IFN DEBPRC,<
	skipn clkque
	outchr ["≤"]
>;IFN DEBPRC
	MOVEM TAC,CLKQUE	;We now have a new head of queue
	MOVEM A,NXTTIM		;What time next interrupt will be. We have to
				;save this as the system will not guarantee
				;that we will be started up exactly when we
				;requested
	CLKINT 1,@-2(P)		;Actual interrupt request
	TURNON [CLKINT]
	JRST RETURN
BEND ENTCLK
SUBTTL DEQUE  - Delete first entry from queue
BEGIN DEQUE
;
;	PUSH P,<queue>
;	PUSHJ P,DEQUE
;
; Process pointer returned in RET
; Destroys TAC, TAC2
;
↑DEQUE:	ENTERLOCK QUELOK	;Interlock against queue modification
;Stack: <queue>,<return address>
	MOVE TAC,-1(P)		;Get queue pointer
	HRRZ RET,(TAC)		;Pick up first in queue
	JUMPE RET,EMPTY
	HLRZ TAC2,%LINK(RET)	;Pick up next in queue
	MOVEM TAC2,(TAC)	;New head of queue
	JUMPN TAC2,.+2		;Last in queue?
	MOVEM TAC2,1(TAC)	;Yes, zero last pointer in queue
	SOSGE 2(TAC)		;Decrement number in queue
	  PUSHJ P,DRYROT
EMPTY:	LEAVELOCK QUELOK	;Interlock against queue modification
	SUB P,[XWD 2,2]		;Flush stack
	JRST @2(P)		;And return
BEND DEQUE
SUBTTL SRHQUE - Search queue and delete entry
BEGIN SRHQUE
;
;	PUSH P,<datum to search for>
;	PUSH P,<queue>
;	PUSHJ P,SRHQUE
;
; Process pointer returned in RET
; Destroys TAC, TAC2
;
; This subroutine searches a queue for an matching datum.  If the queue
; is empty or no entry matches, zero is returned.  If entry matches, the
; first such entry is unlinked from the queue (by remembering ancestor) and
; returned.
;
↑SRHQUE:
	ENTERLOCK QUELOK	;Interlock against queue modification
;Stack: <datum>,<queue>,<return address>
;	  -2      -1		0
	HRRO TAC,-1(P)
	MOVE RET,(TAC)		;First node in queue
	JUMPE RET,RETURN	;Not found
LOOP:	HLRZ TAC2,%DATUM(RET)	;Check datum
	CAMN TAC2,-2(P)
	JRST [	HLRZ TAC2,%LINK(RET)	;Point previous at next
		JUMPE TAC2,SETTL
		JUMPL TAC,SETHD		;Check for head of queue
		HRLM TAC2,%LINK(TAC)	;Normal node
		SKIPA TAC,-1(P)		;Get head of queue
	SETHD:	HRRZM TAC2,(TAC)	;(From above) Special case of head of queue
		SOSGE 2(TAC)		;Decrement number in queue
		  PUSHJ P,DRYROT
		JRST RETURN
	SETTL:	JUMPL TAC,[MOVE TAC,-1(P)	;Watch for empty case
			   SETZB TAC2,1(TAC)	;Zero end marker
			   JRST SETHD]		;Set head to nothingness
		HRRZS %LINK(TAC)	;Mark end of list with zero
		MOVE TAC2,-1(P)		;Get head of queue
		HRRZM TAC,1(TAC2)	;Set tail of queue
		SOSGE 2(TAC2)
		  PUSHJ P,DRYROT
		JRST RETURN ]
	MOVE TAC,RET		;Old previous node
	HLRZ RET,%LINK(TAC)	;New node to check
	JUMPN RET,LOOP
RETURN:	LEAVELOCK QUELOK	;Interlock against queue modification
	SUB P,[XWD 3,3]		;Flush stack
	JRST @3(P)		;And return
BEND SRHQUE
SUBTTL SCHED  - Schedule a Process
BEGIN SCHED
;
;	PUSH P,<PC of process on stack>
;	PUSHJ P,MKPROC
; Returns address of process in RET
; Calls MKPBLK
;
↑SCHED:
IFN DEBPRC,<
	SKIPN DEBUG
	OUTCHR ["+"]
>;IFN DEBPRC
	PUSH P,U		;Save U
	HRRZ U,U		;Interrupt routines like to use left half
	PUSH P,-2(P)		;Create a process
	PUSHJ P,MKPROC
	PUSH P,RET		;Queue it to run
	PUSH P,[RUNQUE]
	PUSHJ P,ENQUE
	AOS RUNWAIT		;Increment number of processes waiting to be run
	POP P,U			;Return U
	POP P,-1(P)		;Flush arg from stack
	POPJ P,			;And return
BEND SCHED
SUBTTL RESCHED- Request to be Rescheduled (also WSCHED)
BEGIN RESCHED
;
; Called by process to let others run or to wait for something.  Called by:
;
;	PUSH P,[<queue to wait in>]
;	PUSHJ P,RESCHED		;to run (use WSCHED to wait)
;
; All AC's and flags are preserved
;
↑RESCHED:
	AOS RUNWAIT		;Increment number of processes waiting to be run
↑WSCHED:
IFN DEBPRC,<
	SKIPE DEBUG
	OUTCHR ["+"]
>;IFN DEBPRC
;**** This should be fixed to avoid temperaries!!! ****
	ENTERLOCK QUELOK	;Impure code, need to interlock
	POP P,TEMP1		;Save return address
	POP P,NEWQUE		;Save new queue
;	exch tac,temp1
;	cail tac,$bgnet
;	caile tac,endcod
;	jrst [	exch tac,temp1
;		exch p,sys.p
;		pushj p,dryrot
;		jrst .-1 ]
	PUSH P,TEMP1		;Put return address back on stack
	PUSHACS
	PUSHJ P,GETPRO		;Get process pointer
	SUB P,[XWD 17,17]
	HRRM P,(TAC)		;Copy of P
;	HLRO TAC,P		;Find top of stack
;	SUBM P,TAC
;	MOVEI TAC,-%PDLIO(TAC)	;This is a process pointer now
	SKIPE INTLEV		;At interrupt level?
	PUSHJ P,DRYROT		;Lose big!!
	MOVE P,SYS.P
	MOVEM P,LAST.P		;For debugging
	SETZM SYS.P		;We're don't need it any more
	PUSH P,TAC
	PUSH P,NEWQUE
	LEAVELOCK QUELOK
	PUSHJ P,ENQUE
;*** The following crock is due to timing race in IMICHW and NETOPN ***
	push p,tac
	move tac,newque
	cain tac,imwque
	jrst [	leavelock imilok
		turnon [intinp]
		jrst foo ]
	cain tac,imsque
	jrst [	turnon [intims]
		jrst foo ]
foo:	pop p,tac
;*** End of IMICHW crock ***
	POPJ P,
INTEGER TEMP1,NEWQUE
BEND RESCHED
SUBTTL DELAY  - Schedule a Process in future
BEGIN DELAY
;
;	PUSH P,<PC of process on stack>
;	PUSH P,<number of tics to wait>
;	PUSHJ P,DELAY
; Returns address of process in RET
; Calls MKPBLK
;
↑DELAY:	PUSH P,-2(P)		;Create a process
	PUSHJ P,MKPROC
	PUSH P,RET		;Queue it to run
	PUSH P,-2(P)		;Time (note P incremented by previous PUSH)
	PUSHJ P,ENTCLK
	SUB P,[XWD 3,3]
	JRST @3(P)
BEND DELAY
SUBTTL POSTPON- Request to be Rescheduled
BEGIN POSTPONE
;
; Called by process to wait a specified length of time
;
;	PUSH P,[<number of tics to wait>]
;	PUSHJ P,POSTPONE
;
; All AC's and flags are preserved
;
↑POSTPONE:
	PUSH P,TAC		;Save TAC
	MOVE TAC,SYS.P		;Pick up system stack point
	PUSH TAC,0		;Push space for process pointer
	PUSH TAC,-2(P)		;Push number of tic to wait
	MOVEM TAC,SYS.P
	POP P,TAC		;Get back TAC
	POP P,-1(P)		;Flush time from stack
	PUSHACS
	PUSHJ P,GETPRO		;Get pointer to process
	SUB P,[XWD 17,17]
	HRRM P,(TAC)		;Copy of P
	SKIPE INTLEV		;At interrupt level?
	PUSHJ P,DRYROT		;Lose big!!
	MOVE P,SYS.P
	MOVEM P,LAST.P		;For debugging
	SETZM SYS.P		;We're don't need to save system stack pointer
	MOVEM TAC,-1(P)		;Set process pointer (save alreadly allocated on stack)
	PUSHJ P,ENTCLK		;Enter into clock queue (args already on stack)
	POPJ P,			;Return to invoker of process
BEND POSTPONE
SUBTTL RUNPROC- Run a Process
BEGIN RUNPROC
;
; Sets up process and runs it.  Returns when done.  Called with:
;
;	PUSH P,<process pointer>
;	PUSHJ P,RUNPROC
;
; Destroys TAC of caller.  Process's AC's and flags are preserved.
;
↑RUNPROC:
IFN DEBPRC,<
	SKIPE DEBUG
	OUTCHR ["-"]
>;IFN DEBPRC
	SOSGE RUNWAIT		;Decrement number of processes waiting to be run
	JRST [PUSHJ P,DRYROT		;Over-SOS'ed it!!!
	      SETZM RUNWAIT
	      JRST .+1]
	POP P,TAC		;Get PC
	EXCH TAC,(P)		;Save and get process pointer
	MOVEM TAC,LASTRUN	;For debugging, remember last process run
	SKIPE SYS.P		;Make sure user process not trying to run another
	PUSHJ P,DRYROT		;Oops!
	MOVEM P,SYS.P		;Save system stack pointer
	HRRZ TAC,%LINK(TAC)	;Pick up pointer to AC's on stack
	MOVSI P,(TAC)		;Load AC's
	BLT P,P
	HRRE 0,-1(P)		;Bless the PC
;	JUMPLE 0,BADPC		;Oops!
	CAIL 0,$BGNET
	CAILE 0,ENDCOD
	JRST BADPC
	POP P,0			;On last AC.
	POP P,LASTPC		;Remember for debugging (we couldn't POPJ and get
				;flags anyway)
	JRST 2,@LASTPC		;Restore flags
BADPC:
	OUTSTR[ASCIZ/Process PC out of bounds./]
	exch p,sys.p
	skipe debug
	pushj p,dryrot
	exch p,sys.p
	OUTSTR[ASCIZ/..Flushed!
/]↔	JRST PREXIT
BEND RUNPROC
SUBTTL PREXIT - Process Exit
BEGIN PREXIT

↑PREXIT:
	PUSHJ P,GETPRO		;Get process pointer
	SKIPE INTLEV		;At interrupt level?
	PUSHJ P,DRYROT		;Lose big!!
	MOVE P,SYS.P		;Get back system's stack pointer
	MOVEM P,LAST.P		;For debugging
	SETZM SYS.P		;We're done with it now.
	PUSH P,TAC		;Kill the dead process
	PUSHJ P,KLPROC
	POPJ P,
BEND PREXIT
SUBTTL GETPRO - Get process pointer from stack pointer
;
;	PUSHJ P,GETPRO
; Returns stack pointer in TAC
;
GETPRO:	HLRO TAC,P		;Find top of stack
	SUBM P,TAC
	MOVEI TAC,1-%PDLIO(TAC)	;This is a process pointer now
	POPJ P,
SUBTTL GTUSID - Get User Id
BEGIN GTUSID

↑GTUSID:MOVE TAC,USEMAP		;Pick up bit map of available slots
	JFFO TAC,GOTONE		;Any
	PUSHJ P,DRYROT		;Lose big!
GOTONE:	MOVE U,TAC2		;Give him/her a number
	MOVSI TAC,400000	;Turn off appropriate bit
	MOVN TAC2,U
	ROT TAC,(TAC2)
	ANDCAB TAC,USEMAP
	AOS USERS		;Increment number of users
	JUMPN TAC,SETU		;If no more slots left,
	MOVE TAC,[SIXBIT/ NTGRF/]	;Then change name so LOGGER
	SETNAM TAC,		;doesn't send us any more mail
	PUSH P,U		;Save while we flush U.ICP
	MOVEI U,U.ICP
	SETZ A,
	PUSHJ P,KLUSER		;Flush the ICP for now
	POP P,U
SETU:	PUSHJ P,GETPRO		;Get process pointer
	HRRM U,%USER(TAC)	;Set correct user
	POPJ P,			;And return ID
	
BEND GTUSID
SUBTTL KLUSER - Kill user and release associated storage	***
BEGIN KLUSER
;
;	MOVE U,<user number>
;	PUSHJ P,KLUSER
;
↑KLUSER:
	JUMPE A,KLUSE2		;Quiet flush.
	SKIPN DEBUG
	jrst [	
		MOVE TAC,A
		PUSHJ P,LOGIT
		XWD 7,[ASCIZ/Killing #/]
		XWD =10,U
		XWD 7,[ASCIZ/:	/]
		XWD =11,TAC
		0
		JRST KLUSE2 ]
	OUTSTR (A)
	OUTSTR[ASCIZ/Killing user number /]
	PUSH P,U
	PUSHJ P,TYPDEC
; Search down the queues and kill any processes belonging to this user
KLUSE2:	PUSH P,A		;Get a permanent AC
	PUSH P,B
	PUSH P,C
	PUSH P,[-1]		;Best be done with interrupts off!
	IMSKCR (P)
	MOVE A,[XWD -NQUES,BEGQUE]	;Pointer to all queues
	MOVEI B,PREXIT		;We will force any process pending for this job to exit
QLOOP:	MOVE TAC,(A)		;Get first entry in queue
	MOVEI C,100		;Limit number of times thru loop
PLOOP:	JUMPE TAC,QEND		;End check
	SOJL C,[PUSHJ P,DRYROT
	        JRST QEND]
	HRRZ TAC2,%USER(TAC)	;One of his/hers?
IFN GRFPRO,<
	CAIGE U,NUSERS
	CAIE TAC2,NUSERS+1(U)
>;IFN GRFPRO
	CAIN TAC2,1(U)		;User number + 1 in process block (for ENTCLK)
	  JRST[	HRRZ TAC2,%PACS(TAC)	;Pointer to acs
		MOVEM B,-1(TAC2)	;Mung PC to run PREXIT when it is invoked.
		JRST .+1 ]
	HLRZ TAC,(TAC)		;Pick up next entry in queue
	JRST PLOOP		;No, look for more
;Current queue has been checked
QEND:	ADDI A,QUESIZ-1		;Any more queues to check?
	AOBJN A,QLOOP		;Yep
	HRRZ TAC,PTYNUM(U)	;Did user get a PTY?
	CAIL U,NUSERS		;Is it really a PTY
	  JRST NOTPTY		;No	
	JUMPE TAC,NOTPTY
;Robert E. Mass Memorial job detach code
	TTYJOB TAC,		;Get job number if any
	JUMPE TAC,NOTPTY
	MOVEI TAC2,211		;Access system's PRJPRG table
	PEEK TAC2,
	ADD TAC2,TAC
	PEEK TAC2,
	HRRZ TAC2,TAC2
	CAIE TAC,'TVR'			;I think i'll try it too.  TVR
	CAIN TAC2,'REM'
	  JRST[	MOVE TAC,PTYNUM(U)	;Clear the input buffer
		MOVEI TAC2,7
		PTJOBX TAC
		MOVEI TAC2,[BYTE (9) 600,600,"D","E","T",12,0]
		PTWRS9 TAC		;Send ↑C ↑C DET <lf>
		SETZ TAC,
		SLEEP TAC,		;Wait a tick for system to gobble
		JRST NOTREM]		;characters
NOTREM:	PTYREL PTYNUM(U)	;Yes, release its PTY
NOTPTY:	SETZM PTYNUM(U)		;Forget we had it
	SETZM PTIFUL(U)		;Forget there might be a process using this channel
	SETOM WHRTAB(U)		;Zap his/her WHERE table entry
	PUSHJ P,KLIMP		;Kill IMP connection
IFN GRFPRO,
<	CAIL U,NUSERS		;Is it a graphics connection being killed?
	  JRST ISGRF
	SETZM DPYFLG-NUSERS(U)	;Zero display mode flag
	ADDI U,NUSERS
	PUSHJ P,KLIMP		;Don't forget graphics connection
	SUBI U,NUSERS
>;IFN GRFPRO,
	MOVSI TAC,400000	;Release user id
	MOVN TAC2,U
	ROT TAC,(TAC2)
	ORM TAC,USEMAP
ISGRF:	SKIPE DEBUG
	OUTSTR CRLF
	INTMSK 1,(P)		;Restore interrupts
	POP P,(P)		;Flush interrupt mask
	POP P,C
	POP P,B			;Restore old acs
	POP P,A
IFN GRFPRO,<
	CAIL U,NUSERS		;Don't decrement user count for graphics
	  POPJ P,		;and return
>;IFN GRFPRO
	PUSHJ P,OUTWHR		;Write out table again
	SOSN TAC,USERS		;If not last user or debugging,
	SKIPE DEBUG
	JRST [	CAIE TAC,NUSERS-1	;Did the last user just disperse?
		  POPJ P,		;  No, done then
		MOVE TAC,[SIXBIT/NETGRF/]	;Then set name to indicate we'll
		SETNAM TAC,		;take another user
		MOVEI U,U.ICP
		PUSH P,[NEWICP]		;Start up new ICP
		PUSHJ P,SCHED
		POPJ P, ]		;And return
	EXIT			;Otherwise, die quietly

;Kill IMP connection
KLIMP:	MOVSI TAC,400000	;Mark IMP connection as flushed
	MOVN TAC2,U
	ROT TAC,(TAC2)
	TDNN TAC,IMPMAP		;If present
	  POPJ P,		;(Otherwise return)
	MOVE A,U		;First, get user number
	IDIVI A,NUSERS		;User number->B, Graphics if A=1
	MOVE B,LSOCKT(U)	;Get local socket number
	LSH A,1			;Offset to socket number is 2
	ADD B,A
	MOVE TAC2,[MTAPE 000,A]	;Close both connections
	DPB U,[POINT 4,TAC2,12]
	MOVEI A,3		;Opcode for TERMINATE
	SETZ C,			;Don't wait, other users!
	XCT TAC2		;MTAPE 000,[TERMINATE]
	CAIN U,U.ICP		;ICP socket?
	  JRST KLIMP2		;  Yeah, skip other side
	ADDI B,1		;Other half of connection
	XCT TAC2		;MTAPE 000,[TERMINATE]
;	MOVE TAC2,[CLOSE 000,]	;Close IMP connection
;	DPB U,[POINT 4,TAC2,12]
;	XCT TAC2
KLIMP2:	ANDCAM TAC,IMPMAP
	MOVE TAC,[RELEASE 000,3] ;Release IMP connection
	DPB U,[POINT 4,TAC,12]
	XCT TAC			;RELEASE CHAN,3
	CAIN U,U.ICP		;ICP socket?
	  POPJ P,		;  Yeah, no buffers used
	MOVEI A,3		;Index for buffer headers
	IMULM U,A
	PUSH P,INHDR(A)		;Release its buffers
	PUSHJ P,KLRBUF
	PUSH P,OUTHDR(A)
	PUSHJ P,KLRBUF
	POPJ P,
BEND KLUSER
SUBTTL FNDPTY - Find user number from pseudoteletype line
BEGIN FNDPTY
;
;
;	PUSH P,[<PTY number>]
;	PUSHJ P,FNDPTY
;	<failure return>
;
; Returns with user number in U.  Destroys TAC
;
FNDPTY↑:MOVSI U,-NUSERS		;Set up for search
	HRRZS -1(P)		;Flush status bits
FNDPT2:	HRRZ TAC,PTYNUM(U)	;Get pty number
	CAMN TAC,-1(P)		;That one?
	JRST [	AOS (P)			;Yes, success
		HRRZ U,U		;Flush AOBJN part
		JRST RETURN ]
	AOBJN U,FNDPT2		;No, try again
	SETO U,			;Lose!
RETURN:	POP P,-1(P)		;Flush stack
	POPJ P,			;And return
BEND FNDPTY
SUBTTL MKRBUF - Make Ring Buffers for System I/O
BEGIN MKRBUF
;
;	PUSH P,[<number of buffers>]
;	PUSH P,[<buffer size>]
;	PUSHJ P,MKRBUF
;
↑MKRBUF:PUSH P,A		;Save A
	MOVE TAC,-2(P)		;Check size
	ADDI TAC,2
	CAILE TAC,PROCSZ
	PUSHJ P,DRYROT		;Oops!
	PUSHJ P,MKPBLK		;Make a process block
	SUBI RET,PROCSZ-2	;Point to second word
	HRLZ TAC,-2(P)		;Pick up buffer size
	HRR TAC,RET		;Make pointer to next buffer (self)
	MOVEM TAC,(RET)
	PUSH P,RET		;Save address of first buffer
;Stack: <number of buffers>,<buffer size>,<return address>,<previous buffer>,<next buffer>
LOOP:	MOVE A,RET		;Save address of previous buffer
	SOSG -4(P)		;Enough?
	JRST RETURN		;Yes, return
	PUSHJ P,MKPBLK		;Get another process block
	SUBI RET,PROCSZ-2	;Point to second word
	HRL A,-3(P)		;Pick up buffer size
	MOVEM A,(RET)		;Point this buffer at previous buffer
	JRST LOOP		;Put size in and test for end
RETURN:	HRRM RET,@(P)		;Point last buffer to first to close the ring
	HRLI RET,400000		;Turn on high order bit to indicate unused buffer
	MOVE A,-1(P)		;Restore A
	SUB P,[XWD 5,5]		;Flush stack
	JRST @3(P)		;And return

BEND MKRBUF
SUBTTL KLRBUF - Kill Ring Buffers
BEGIN KLRBUF
;
;	PUSH P,<buffer pointer>
;	PUSHJ P,KLRBUF
;
↑KLRBUF:PUSH P,A		;Get a handy permanent AC
	HRRZS A,-2(P)		;Pick up first buffer
LOOP:	ADDI A,PROCSZ-2		;Make it into process block pointer
	PUSH P,A		;Get ready to kill process block
	HRRZ A,2-PROCSZ(A)	;Pick up next buffer before we do though
	PUSHJ P,KLPBLK		;Kill it here
	CAME A,-2(P)		;Back to first yet?
	JRST LOOP		;No, flush another buffer and try again
	POP P,A			;Get back that ac
	POP P,-1(P)		;Flush arg
	POPJ P,			;And return
BEND KLRBUF
SUBTTL ENLOCK - Enter interlock
BEGIN ENLOCK
;
; Called by:
;	PUSHJ P,ENLOCK
;	<interlock block>
; Preserves all AC's
; Note: this is generated by macro: ENTERLOCK
;
; *** The following is a kludge ***
↑ENLOCK:AOSE LOKCNT		;Already in interlock?
	  JRST RETURN		;Yes, just return then
	push p,(p)
	pop p,beglok
	SKIPE INTLEV		;or at interrupt level
	  JRST RETURN		;Yes, just return then
IFN NEWSW,<	PUSHJ P,DRYROT	>
	SETOM LOKMSK		;Save mask and disable interrupts
	IMSKCR LOKMSK
RETURN:	AOS (P)			;Skip over interlock block
IFN DEBPRC,<
	skipe debug
	outchr ["<"]
;We have to have a ">" to assemble correctly!!!!!
>;IFN DEBPRC
	POPJ P,
BEND ENLOCK
SUBTTL DELOCK - Leave interlock
BEGIN DELOCK
;
; Called by:
;	PUSHJ P,DELOCK
;	<interlock block>
; Preserves all AC's
; Note: this is generated by macro: LEAVELOCK
;
; *** The following is a kludge ***
↑DELOCK:
	skipge lokcnt		;*** Bug trap ***
	jrst [	pushj p,dryrot
		jrst return ]
	SOSL LOKCNT		;In another interlock?
	  JRST RETURN
;↓↓↓ Bug trap
	PUSH P,TAC
	MOVE TAC,@-1(P)
	CAME TAC,@BEGLOK
	PUSHJ P,DRYROT
	MOVE TAC,-1(P)
	MOVEM TAC,LSTLOK#
	POP P,TAC
;↑↑↑ Bug trap
	SKIPE INTLEV		;or at interrupt level
	  JRST RETURN		;Yes, just return then
IFN NEWSW,<	PUSHJ P,DRYROT	>
	IMSKST LOKMSK		;Restore mask
RETURN:	AOS (P)			;Skip over interlock block
IFN DEBPRC,<
	skipe debug
;The following must be preceded with "<" to assemb correctly
	outchr [">"]
>;IFN DEBPRC
	POPJ P,
BEND DELOCK
SUBTTL LOKENB - Enable interrupts inside an interlock
;
; Called by TURNON macro
;	SKIPL LOKCNT
;	PUSHJ P,LOKENB
;	IMSKST [<interrupt bits>]
;	
; Must preserve all ACs
;
; *** The following is a kludge ***
↑LOKENB:SKIPE INTLEV		;If at interrupt level, do IMSKST
	POPJ P,
	PUSH P,@(P)		;Push address of word containing bits
	PUSH P,@(P)		;Push actual bits
	MOVEM TAC,-1(P)		;Save TAC
	POP P,TAC		;Get interrupt bits
	ORM LOKMSK		;Turn them on in the mask
	POP P,TAC		;Restore TAC
	AOS (P)			;Skip over IMSKST
	POPJ P,			;Return
SUBTTL LOKWAI - Wait for interlock
IFN NEWSW,<
BEGIN LOKWAI
;
; Called by:
;	AOSE <interlock block>
;	PUSHJ P,LOKWAI
; Preserves all AC's
; Note: this is generated by macro: ENTERLOCK
;
↑LOKWAI:
BEND LOKWAI
>;IFN NEWSW
SUBTTL USERMO - Enter user mode				***
BEGIN USERMODE
↑USERMODE:			;At interrupt level and want to do a waiting UUO.
	SKIPN INTLEV
	POPJ P,			;Dept. of Redundency Dept.
	MOVEM 17,ACSAVE+17	;Oh, dear!  Let's see what see can do...
	MOVEI 17,ACSAVE
	BLT 17,ACSAVE+16	;Save them acs
	SETOM OLDMSK		;Save mask
	IMSKCR OLDMSK
	MOVE TAC,JOBTPC		;Don't forget the PC
	MOVEM TAC,OLDPC
	outchr ["U"]
	UWAIT			;Wait for UUO to finish
	DEBREAK
	MOVEM 17,OLDACS+17	;Save running processes acs
	MOVEI 17,OLDACS
	BLT 17,OLDACS+16
	MOVSI P,ACSAVE		;Get back our interrupt acs
	BLT P,P
	SETZM INTLEV		;We're no longer at interrupt level
	POPJ P,			;Return in user mode

ARRAY ACSAVE[20]

BEND USERMODE
SUBTTL LOGIT  - Log messages
BEGIN LOGIT

↑LOGIT:	
	PUSH P,RET		;Save RET
	PUSH P,[-1]		;Turn off interrupts
	IMSKCR (P)
	MOVE RET,-2(P)
	MOVEM RET,SAVEPC
	MOVE RET,LOGIOWD
	PUSH RET,[HEADER]
	PUSH RET,2
	PUSH RET,3
	PUSH RET,4
	MOVEM RET,OTHERP
	PUSH P,['NETGRF']
	PUSH P,['LOG   ']
	PUSH P,['NETTVR']
	PUSH P,[MSGBUF]
	PUSH P,[PUSHJ P,SWITC2]
	PUSHJ P,LOGMSG↑
	JUMPN RET,LOGIT2
LOGIT1:	AOS RET,SAVEPC
	SKIPE -1(RET)
	JRST LOGIT1
LOGIT2:	MOVE RET,SAVEPC
	MOVEM RET,-2(P)
	POP P,RET
	IMSKST RET
	POP P,RET
	POPJ P,
SWITCH:	SKIPE DEBUG
	OUTCHR RET
SWITC2:	PUSH P,2
	PUSH P,3
	PUSH P,4
	EXCH P,OTHERP
	POP P,4
	POP P,3
	POP P,2
CPOPJ:	POPJ P,
HEADER:	MOVEI RET,"∂"		;RCV style header
	PUSHJ P,SWITCH
	ACCTIM RET,		;Include date and time
	PUSH P,RET
	PUSH P,[PUSHJ P,SWITCH]
	PUSHJ P,WRDAYT↑
	PUSH P,[[ASCIZ/ JOB /]]
	PUSH P,[PUSHJ P,SWITCH]
	PUSHJ P,WRASCZ↑
	PJOB RET,
	PUSH P,RET
	PUSH P,[=10]
	PUSH P,[PUSHJ P,SWITCH]
	PUSHJ P,WRINT↑
	PUSH P,[[ASCIZ/  NETGRF: /]]
	PUSH P,[PUSHJ P,SWITCH]
	PUSHJ P,WRASCZ↑
NXTITM:	MOVE RET,@SAVEPC	;Get next item
	AOS SAVEPC
	JUMPE RET,LSTITM	;Zero marks end
	PUSH P,RET		;Save item
	HLRZ RET,RET		;Get type
	CAIL RET,6		;Legal?
	CAILE RET,=15
	SKIPA RET,[JRST NXTITM]	;Unknown type
	MOVE RET,ITMTAB-6(RET)	;Get address of routine to call
	EXCH RET,(P)		;Restore RET and invoke
	POPJ P,			;conversion routine
LSTITM:	MOVEI RET,15
	PUSHJ P,SWITCH
	MOVEI RET,12
	PUSHJ P,SWITCH
	SETZ RET,
	PUSHJ P,SWITCH

ITMTAB:	[PUSH P,(RET)		;6 - SIXBIT
	 PUSH P,[PUSHJ P,SWITCH]
	 PUSHJ P,WRSIX↑
	 JRST NXTITM ]
	[HRLI RET,(<POINT 7,0>)	;7 - ASCIZ (Immediate)
	 PUSH P,RET
	 PUSH P,[PUSHJ P,SWITCH]
	 PUSHJ P,WRASCZ↑
	 JRST NXTITM ]
	[PUSH P,(RET)		;8 - OCTAL
	 PUSH P,[8]
	 PUSH P,[PUSHJ P,SWITCH]
	 PUSHJ P,WRINT↑
	 JRST NXTITM ]
	[JRST NXTITM ]		;9 - UNDEF
	[PUSH P,(RET)		;10 - DECIMAL
	 PUSH P,[=10]
	 PUSH P,[PUSHJ P,SWITCH]
	 PUSHJ P,WRINT↑
	 JRST NXTITM ]
	[HRLI RET,(<POINT 7,0>)	;11 - ASCIZ
	 PUSH P,(RET)
	 PUSH P,[PUSHJ P,SWITCH]
	 PUSHJ P,WRASCZ↑
	 JRST NXTITM ]
	[JRST NXTITM]		;12 - UNDEF
	[TLZ RET,		;13 - OCTAL (Immediate)
	 PUSH P,RET
	 PUSH P,[6]
 MOD13A: PUSH P,[PUSHJ P,SWITCH]
	 PUSHJ P,WROCT↑
	 MOVEI RET," "
	 PUSHJ P,SWITCH
	 JRST NXTITM]
	[PUSH P,(RET)		;14 - OCTAL
	 PUSH P,[=12]
	 JRST MOD13A]
	[PUSH P,(RET)		;15 - SYMBOLIC
	 HRRZS (P)
	 PUSH P,[PUSHJ P,SWITCH]
	 PUSHJ P,WRSYMB↑
	 JRST NXTITM ]

INTEGER LOGSUB,SAVEPC,OTHERP
ARRAY MSGBUF[200]
BEND LOGIT
SUBTTL SETCHK - Initialize checksum of pure code

IFN CHKSW,<

↑SETCHK:PUSH P,[-1]		;Turn off interrupts
	IMSKCR (P)
	PUSHJ P,CALCHK		;Calculate checksum
	MOVEM RET,CHKSUM	;Save it somewhere
;Now, we make a temperary file which will go away when we EXIT.  There is
;is no straightforward way of doing this, so we get to fake it.  First, we
;write the file and close it.  Now, it is known to the file system.  Then
;do a LOOKUP on each of two channels.  We do a deletion on second channel,
;but the file only removed from its directory and doesn't go away yet,
;because it is being read on first channel!  We IOPUSH it so it doesn't get
;accidentally closed, and the file stays around until we EXIT, at which point
;the system decides that no one else is reading it and it really deletes it!
	IOPUSH CHKCHN,['SETCHK']	;Save a channel
	PUSHJ P,CHKERR
	OPEN CHKCHN,CHKDEV		;Open device
	PUSHJ P,CHKERR
	PJOB TAC,
	IDIVI TAC,=10			;Put job number into filename
	DPB TAC,[POINT 4,CHKNAM,35-6]
	DPB TAC2,[POINT 4,CHKNAM,35]
	SETZM CHKNAM+3			;(Stupid LOOKUP clobbers PPN)
	ENTER CHKCHN,CHKNAM		;ENTER file
	PUSHJ P,CHKERR
	OUT CHKCHN,CHKIOWD		;Write out core onto file
	CAIA
	PUSHJ P,CHKERR
	CLOSE CHKCHN,			;Done writing, close it!
	SETZM CHKNAM+3			;(Stupid LOOKUP clobbers PPN)
	LOOKUP CHKCHN,CHKNAM		;Open it on first channel (one we'll use)
	PUSHJ P,CHKERR
	IOPUSH CHKCHN,['CHKFIL']
	PUSHJ P,CHKERR
	OPEN CHKCHN,CHKDEV		;Open device
	PUSHJ P,CHKERR
	SETZM CHKNAM+3			;(Stupid LOOKUP clobbers PPN)
	LOOKUP CHKCHN,CHKNAM		;Open it on second channel
	PUSHJ P,CHKERR
	RENAME CHKCHN,ZERO4		;and delete it!
	PUSHJ P,CHKERR
	CLOSE CHKCHN,
	RELEASE CHKCHN,
	IOPOP CHKCHN,['SETCHK']
	PUSHJ P,CHKERR
	IMSKST (P)			;Re-enable interrupts
	POP P,(P)
	POPJ P,
CHKERR:	pushj p,dryrot
	popj p,
ZERO4:	BLOCK 4		;For doing deletes
CHKDEV:	17
	SIXBIT/DSK/
	0
>;IFN CHKSW
SUBTTL SYSCHK - Checksum pure code and fix if pure code modified
IFN CHKSW,<
BEGIN SYSCHK

↑SYSCHK:PUSHJ P,CALCHK		;Calculate checksum
	CAMN RET,CHKSUM		;Correct?
	POPJ P,			;Yes, return quickly
	PUSH P,[-1]		;Turn off interrupts
	IMSKCR (P)
	SKIPE DEBUG
	OUTSTR[ASCIZ/Checksum failure!!!
/]
	IOPUSH CHKCHN,['SYSCHK']	;Save I/O
	PUSHJ P,DRYROT
	MOVE A,P		;Save PDL pointer
	HLRE B,P		;See how much PDL we have left for
	MOVN B,B		;saving garbage on
	SUBI B,=10
	CAIL B,=10		;Don't bother with more than 10 errors
	MOVEI B,=10
	MOVE C,[IOWD =40,CHKTAB]	;We're generating code to print errors
	IOPOP CHKCHN,['CHKFIL']	;Get back file whereon pure code is saved
	PUSHJ P,DRYROT
	USETI CHKCHN,1		;Back up to beginning of file
	MOVE TAC,CHKIOWD	;Pointer to pure code
	ADDI TAC,1
	SETZM CHKERS		;Clear error count
CHKNXB:	IN CHKCHN,[IOWD 200,CHKBUF	;Read in a buffer
		   0]
	JRST CHKIOK
	STATO CHKCHN,IODEND
	PUSHJ P,DRYROT
CHKIOK:	MOVE TAC2,[XWD -200,CHKBUF]	;Set up buffer pointer for compares
CHKLP:	MOVE RET,(TAC2)		;Get word from disk
	CAME RET,(TAC)		;Compare with word in core
	JRST CMPBAD
FIXDON:	AOBJP TAC,CHKDON	;Repeat until done with checking
	AOBJN TAC2,CHKLP	;Repeat until with each buffer
	JRST CHKNXB		;Then get another buffer
CMPBAD:	SOJL B,NOROOM
	PUSH C,[XWD =13,0]	;Compile: Address of error
	HRRM TAC,(C)
	PUSH C,[XWD =14,0]	;Compile: Disk contents
	HRRM TAC,(C)
	PUSH P,(TAC)		;Save old bad contents
	PUSH C,[XWD =14,0]	;Compile: Memory contents
	HRRM P,(C)
	PUSH C,[XWD 7,[ASCIZ/
/]]				;Compile <CR><LF>
NOROOM:	MOVEM RET,(TAC)		;Now fix up error!
	AOS CHKERS		;Increment number of errors
	JRST FIXDON		;And go look for more!
;Done with compare/fixing, now reset I/O and print errors
CHKDON:	JUMPLE B,CHKDO2
	PUSH C,[0]
	PUSH C,[POPJ P,]
CHKDO2:	IOPUSH CHKCHN,['CHKFIL']	;Put copy pure code somewhere safe again
	PUSHJ P,DRYROT
	IOPOP CHKCHN,['SYSCHK']	;Get back old I/O channel
	PUSHJ P,DRYROT
	PUSHJ P,CHKMSG		;Jump into code we're compiled!
	MOVE P,A		;Throw away garbage on stack
	IMSKST (P)		;Re-enable interrupts
	POP P,(P)		;More stack garbage
	POPJ P,			;And we're done
BEND SYSCHK
>;IFN CHKSW
SUBTTL CALCHK - Calculate checksum
IFN CHKSW,<
CALCHK:	SETZ RET,		;Zero checksum
	MOVE TAC,CHKIOWD	;Get IOWD pointer for pure code
CALCH2:	ROT RET,1		;So single bit clobbered gets seen
	ADD RET,1(TAC)		;The index of 1 is because it's an IOWD
	AOBJN TAC,CALCH2	;Repeat for all of the pure code
	POPJ P,			;Return checksum in RET
>;IFN CHKSW
;-------------------------------------------------
SUBTTL IMPOCNT- Return number of bytes which we can send IMP without waiting
BEGIN IMPOCNT
;
;	MOVE U,<user number>
;	PUSHJ P,OUTCNT
;
↑IMPOCNT:
	PUSH P,A
	MOVEI A,MTSIZE			;Get pointer to MTAPE block
	IMULM U,A
	MOVEI TAC,16
	MOVEM TAC,MTBLKS(A)
	MOVE TAC,[MTAPE 000,MTBLKS(A)]
	DPB U,[POINT 4,TAC,12]
	XCT TAC				;MTAPE CHAN,[GET_ALLOC...]
	MOVE TAC,MTBLKS+7(A)		;Get number of bits he has left
	IDIVI TAC,=8096-=36		;Divide by number of bits per message
					;(assume we one word's worth to boundaries)
	SKIPN MTBLKS+10(A)		;Any messages at all?
	JUMPE TAC,USEMES		;No, then skip it!
	CAMLE TAC,MTBLKS+10(A)		;Enough messages left?
	JRST [	MOVEI TAC,=8096-=36		;No, see how well we can do with
		IMUL TAC,MTBLKS+10(A)		;existing messages.
		JRST USEMES ]
	MOVE TAC,MTBLKS+7(A)		;Get number of bits
USEMES:	MOVE RET,BYTSIZ(U)
	IDIVM TAC,RET			;Divide by bytes to return number of bytes
	POP P,A
	POPJ P,
BEND IMPOCNT
SUBTTL IMPOCHR- Send character to IMP
BEGIN IMPOCHR
;
; Called with:
;
;	MOVE U,<user number>
;	MOVE RET,<character>
;	PUSHJ P,IMPOCHR
;
; All other acs are preserved.
; If not enough space is left, then the process is delayed.
;
↑IMPOCHR:
IFN DKPRO,<
	SKIPN DKFLAG(U)		;Is it a faked graphics channel?
	JRST [	SKIPN DKOACT(U)	;No, but check for need of trailer word
		JRST NOTAIL
		PUSH P,RET
		MOVEI RET,DKESC	;Send graphics trailer code
		PUSHJ P,IMPOC2
		MOVEI RET,DKEND
		PUSHJ P,IMPOC2
		SETZM DKOACT(U)
		POP P,RET
	NOTAIL:	CAIGE RET,NUSERS	;If in TELNET mode
		CAIE RET,DKESC		; and D. King escape to output
		JRST IMPOC2
		SKIPE DKFLAG+3(U) 	; and D. King in use
		PUSHJ P,IMPOC2		; Then double the escape!
		JRST IMPOC2 ]
	PUSH P,RET		;Save byte to send
	SUBI U,NUSERS		;Get into telnet channel
	SUBI A,3*NUSERS
	SKIPE DKOACT(U)		;Graphics last?
	JRST NOHEAD		;  Yes, don't send header
	MOVEI RET,DKESC		;No, send header word
	PUSHJ P,IMPOC2
	MOVEI RET,DKBEG
	PUSHJ P,IMPOC2
	SETOM DKOACT(U)
NOHEAD:	POP P,RET		;Get byte back to send
	PUSHJ P,IMPOC2		;Send character
	CAIE RET,$IAC		;IAC or
	CAIN RET,DKESC		;D.K. escape?
	PUSHJ P,IMPOC2		;Yes, double it
	ADDI U,NUSERS
	ADDI A,3*NUSERS
	POPJ P,
>;IFN DKPRO
IMPOC2:	SOSGE BYTLFT(U)		;Enough space for one more
	JRST EMPCHK		;Maybe not, we'll see though
	SOSG @IMOPCNT(U)	;Decrement number of characters left
	PUSHJ P,IMPOUT		;Do output
	IDPB RET,@IMOPPTR(U)
	AOS BYTUSED(U)		;Remember that we used this byte
	JFCL			;Space for OUTCHR RET
	POPJ P,
;Our count of the number of bytes left is exhausted.  See if the system has
;moved any since we checked last.
EMPCHK:	PUSH P,TAC		;Save all this wonderful stuff
	PUSH P,TAC2
	PUSH P,RET
	AOS IMOCNC		;For statistics
	PUSHJ P,IMPOCNT		;Look again and see how much is left in system
	SUB RET,BYTUSED(U)	;less that in our buffer
	JUMPLE RET,EMPTY	;It really is empty
MORLFT:	MOVEM RET,BYTLFT(U)	;Remember number for fast access
RETRY2:	POP P,RET
	POP P,TAC2
	POP P,TAC
	JRST IMPOC2
EMPTY:	AOS IMOEMC		;For statistics
	PUSHJ P,IMPOUT		;Output what's in our buffers before waiting
	PUSH P,A		;Counter for timeout and deciding how long to wait
RETRY:	AOS IMOEMS
;;; We underestimate to be sure, so negative is OK.
;	SKIPE RET		;Better be zero
;	PUSHJ P,DRYROT		;Lose big!
	SKIPA A,[=30]		;Wait a half a second the first time
	ADDI A,=60		;Wait a second longer each time
	CAIL A,=60*=85		;Give up after we've waited about an hour (T = t(t+1)/2)
	JRST [	MOVEI A,[ASCIZ/User host has not accepted output in an hour.  User flushed.
/]↔		JRST KLUSER ]
	PUSH P,A		;Wait a while before trying again
	PUSHJ P,POSTPONE
	PUSHJ P,IMPOCNT		;Look again and see how much is left in system
	SUB RET,BYTUSED(U)	;less that in our buffer
	JUMPLE RET,RETRY
	POP P,A			;We got something, now we may proceed
	JRST MORLFT
BEND IMPOCHR
SUBTTL IMPOUT - Output buffer to IMP
BEGIN IMPOUT
;
; Called with:
;
;	MOVE U,<user number>
;	PUSHJ P,IMPOUT
;
; All other acs are preserved.
; It is assumed that the caller know it will not wait!
;
↑IMPOUT:
IFN DKPRO,<
	SKIPE DKFLAG(U)			;If faked channel,
	  JRST[	SUBI U,NUSERS		;  Flush TELNET channel instead
		PUSHJ P,IMPOU1
		ADDI U,NUSERS
		POPJ P,]
>;IFN DKPRO
IMPOU1:	PUSH P,TAC			;Get an ac
	PUSH P,TAC2			;Get another
	LDB TAC,[POINT 6,@IMOPPTR(U),5]	;Pick up position field
	SUBI TAC,4			;Turn on appropriate bits
	JUMPLE TAC,ALLUSED
	ASH TAC,-3			;causing remaining bytes in word
	MOVEI TAC2,1			;not to be sent
	ASH TAC2,(TAC)
	SUBI TAC2,1
	MOVE TAC,@IMOPPTR(U)
	ORM TAC2,(TAC)
ALLUSED:MOVSI TAC,(<OUT>)		;Do an OUT
	DPB U,[POINT 4,TAC,12]
	CAIL U,GRFMUL*NUSERS		;Bug trap for U out of bounds
	PUSHJ P,DRYROT
	XCT TAC
	SKIPA
;	PUSHJ P,IMPERR			;Error return, see why
	  jrst[	movei a,[asciz/Error on output.
/]↔		pushj p,kluser
		jrst prexit ]
	SETZM BYTUSED(U)		;Nothing in buffers now
	POP P,TAC2
	POP P,TAC
	POPJ P,				;Just in case it got fixed (fat chance)
BEND IMPOUT
SUBTTL IMPSTR - Output string to IMP
BEGIN IMPSTR
;
;	PUSH P,[[ASCIZ/<msg>/]]
;	PUSHJ P,IMPSTR
; Destroys TAC, RET
;
↑IMP8STR:SKIPA TAC,[POINT 8,0]	;For 8 bit strings
↑IMPSTR:MOVSI TAC,(<POINT 7,0>)	;For 7 bit strings
	TDNN TAC,-1(P)		;Make sure we're not blasting exisiting pointer
	HLLM TAC,-1(P)
	PUSH P,A
	MOVEI A,3		;Figure offset in OUTHDR
	IMULM U,A
LOOP:	ILDB RET,-2(P)
	JUMPE RET,[PUSHJ P,IMPOUT	;End of string, flush buffers
		   SETZM BYTLFT(U)	;Force reassessment of bytes remaining
		   POP P,A		;Restore A
		   POP P,-1(P)		;Flush argument
		   POPJ P,]		;Now, return
	PUSHJ P,IMPOCHR
	JRST LOOP

BEND IMPSTR
SUBTTL IMICHS - Skip if character ready from IMP
BEGIN IMICHS
;
; Called with:
;
;	MOVE A,<3*user number>
;	MOVE U,<user number>
;	PUSHJ P,IMICHS
;	<failure return>
;
; All other acs are preserved. Character is returned in RET
;
↑IMICHS:
IFN DKPRO,<
	SKIPN DKIACT(U)		;Is this channel enabled?
	POPJ P,			;  No, that was quick!
	SKIPE DKFLAG(U)		;Is this a DK graphics channel?
	JRST [	SUBI U,NUSERS		;Yes,  switch to TELNET channel
		SUBI A,3*NUSERS
	GRETRY:	SKIPN DKESCF(U)		;Escape seen?
		JRST GNOESC		;  No
		PUSHJ P,IMICH1		;Get a character
		JRST GRFRET		;  None immediately available
		SETZM DKESCF(U)		;Clear escape flag
		CAIN RET,DKEND		;Is it end of graphics?
		JRST [	SETOM DKIACT(U)		;Yes, enable TELNET
			SETZM DKIACT+NUSERS(U)	;Disable graphics
			SKIPE DEBUG
			OUTCHR ["⊃"]
			PUSHJ P,WAKEPR		;Wake any waiters
			JRST GRFRET ]		;Then failure return
	GNOESC:	SKIPE DKIHI(U)		;Is there a high order bit yet?
		JRST GETLOW		;  Yes, look for low one
		PUSHJ P,IMICH1		;Is there anything there?
		JRST GRFRET		;  No,
		CAIN RET,DKESC		;Escape?
		JRST [	SETOM DKESCF(U)	;  Yes, set escape flag
			JRST GRETRY ]
		ROT RET,4		;Make into high order part
		TLO RET,1		;Make sure its nonzero
		MOVEM RET,DKIHI(U)	;Save with left half as flag
	GETLOW:	PUSHJ P,IMICH1		;Look for low order part
		JRST GRFRET		;  Not found
		CAIN RET,DKESC		;Escape?
		JRST [	SETOM DKESCF(U)	;  Yes, set escape flag
			JRST GRETRY ]
		ANDI RET,17		;Flush garbage
		ADD RET,DKIHI(U)	;Add high order part
		ANDI RET,377		;Flush extra garbage
		SETZM DKIHI(U)		;Clear waiting for low indication
		AOS (P)			;Successful (skip) return
	GRFRET:	ADDI U,NUSERS
		ADDI A,3*NUSERS
		POPJ P, ]
TRETRY:	SKIPE DKESCF(U)
	JRST [
		PUSHJ P,IMICH1		;Get a character
		POPJ P,			;  None immediately available
		SETZM DKESCF(U)		;Clear escape
		CAIE RET,DKBEG		;Begin graphics?
		JRST .+1 		;  No, accept anything else as text
		SETZM DKIACT(U)		;Turn off TELNET channel
		SETOM DKIACT+NUSERS(U)	;Turn on graphics channel
		SKIPE DEBUG
		OUTCHR ["⊂"]
		ADDI U,NUSERS		;And take nonskip return
		PUSHJ P,WAKEPR
		SUBI U,NUSERS		;After waking any waiters.
		POPJ P,]
	PUSHJ P,IMICH1		;Get a character, if any
	POPJ P,			;  None, no skip return
	CAIGE U,NUSERS		;Is this graphics channel?
	CAIE RET,DKESC		;    Or not beginning of graphics?
	JRST SKPRET		;  Yes, return quickly
	SKIPN DKFLAG+NUSERS(U)	;Is DK format in use?
	JRST SKPRET		;  No, use this character
	SETOM DKESCF(U)		;Indicate ESC seen
	JRST TRETRY
SKPRET:	AOS (P)
	POPJ P,
>;IFN DKPRO
IMICH1:	SOSLE INHDR+2(A)		;Anything there?
	JRST GOTINP
	PUSH P,TAC
	PUSH P,TAC2
	ENTERLOCK IMILOK		;Interlock against IMP input
	MOVE TAC,[MTAPE 000,[10]]	;Skip if input present
	DPB U,[POINT 4,TAC,12]
	XCT TAC				;MTAPE CHAN,[INPSKP]
	JRST [;	SETZM PTIFUL(U)
		SETZM ALLINP
		LEAVELOCK IMILOK	;Interlock against IMP input
		TURNON [INTINP]		;Turn on IMP input wait
		POP P,TAC2
		POP P,TAC
		POPJ P, ]
	LEAVELOCK IMILOK		;Interlock against IMP input
	PUSHJ P,IMPIN			;Yes, read another buffer
	POP P,TAC2
	POP P,TAC
GOTINP:	ILDB RET,INHDR+1(A)		;Get character
	JUMPE RET,[LDB RET,[POINT 6,INHDR+1(A),5]
		   PUSH P,RET+1			;If null, check if it's real
		   IDIVI RET,=8				;Check ignore bit
		   POP P,RET+1
		   LDB RET,[POINT 1,@INHDR+1(A),35	;(Use a table, it's
			    POINT 1,@INHDR+1(A),34	;more efficient here).
			    POINT 1,@INHDR+1(A),33
			    POINT 1,@INHDR+1(A),32](RET)
		   JUMPN RET,IMICH1			;Try again if to be ignored
		   JRST .+1 ]				;It's good, use it!
	AOS (P)
	POPJ P,

;Wait for character from IMP
↑IMICHW:
	ENTERLOCK IMILOK
	PUSHJ P,IMICHS			;Character ready
	JRST DOWAIT			;  No, wait for one
	LEAVELOCK IMILOK
	POPJ P,				;  Yes, return it
DOWAIT:
	SETZM PTIFUL(U)			;Losing timing race here
;**** Crock: LEAVELOCK IMILOK and TURNON [INTINP] in  WSCHED !!!! ****
	PUSH P,TAC
	PUSHJ P,GETPRO
	HRLM U,%DATUM(TAC)		;Code to wait on
	POP P,TAC
	PUSH P,[IMWQUE]
	PUSHJ P,WSCHED			;Schedule to wait
	JRST IMICHW

IFN DKPRO,<
;Wake process waiting for IMP input upon switching channels
WAKEPR:	
	PUSH P,A		;Save a few ACs
	PUSH P,B
	PUSH P,U		;Look for a process waiting for IMP input
	PUSH P,[IMWQUE]
	PUSHJ P,SRHQUE
	JUMPN RET,[		;Yes, give him the interrupt
		PUSH P,RET		;Give him good service (could be ↑C)
		PUSH P,[PRIQUE]
		PUSHJ P,ENQUE
		AOS RUNWAIT		;Another process waiting for service
		JRST WAKEDN ]
	HRRZ A,U
	IDIVI A,NUSERS		;No one waiting, create a process to handle it
	PUSH P,[IMISER↔GRISER](A);of the appropriate flavour
	PUSHJ P,SCHED
	SETOM PTIFUL(U)
WAKEDN:	POP P,B
	POP P,A
	POPJ P,
>;IFN DKPRO
BEND IMICHS
SUBTTL IMPIN  - Input buffer from IMP
BEGIN IMPIN
;
; Called with:
;
;	MOVE U,<user number>
;	PUSHJ P,IMPIN
;
; TAC is destroyed. All other acs are preserved.
; It is assumed that the caller know it will not wait!
;

IMPIN↑:	MOVSI TAC,(<IN 000,>)	;Read a buffer
	DPB U,[POINT 4,TAC,12]
	XCT TAC			;IN CHAN,
	POPJ P,
;	PUSHJ P,IMPERR		;Oops, read error!
	movei a,[asciz/Error on input. /]	;Tell loser why job is being killed
	push p,a
	pushj p,impstr
	jrst kluser		;Now, flush the loser (chances are however, IMPOUT
				;will flush him first)
BEND IMPIN
SUBTTL Misc. output routines:  TYPOCT,TYPDEC,DRYROT

TYPOCT:	POP P,TAC
	PUSH P,[=8]
	PUSH P,[OUTCHR 1]
	PUSH P,TAC
	JRST WRINT↑
TYPDEC:	POP P,TAC
	PUSH P,[=10]
	PUSH P,[OUTCHR 1]
	PUSH P,TAC
	JRST WRINT↑
TYPSIX:	POP P,TAC
	PUSH P,[OUTCHR 1]
	PUSH P,TAC
	JRST WRSIX↑

DRYROT:	PUSH P,[-1]		;Save mask and disable interrupts
	IMSKCR (P)
	PUSH P,TAC
	PUSH P,RET
	MOVE TAC,['ERRGRF']		;No more customers, please
	SETNAM TAC,
	MOVE TAC,-3(P)			;PC for error routine.
	SKIPE INTLEV
	JRST [	PUSHJ P,USERMODE
		PUSHJ P,LOGIT
		XWD 7,[ASCIZ"Horrible error at interrupt level!  PC/ "]
		XWD =15,TAC
		XWD 7,[ASCIZ/ = /]
		XWD =14,TAC
		0
;		FATAL(Horrible error at interrupt level!)
;		POPJ P,]
		jrst dryro1 ]
	PUSHJ P,LOGIT
	XWD 7,[ASCIZ"Horrible error!  PC/ "]
	XWD =15,TAC
	XWD 7,[ASCIZ/ = /]
	XWD =14,TAC
	0
	PUSH P,['   TVR']
	PUSH P,[[ASCIZ/;; DRYROT in NETGRF!
/]]↔	PUSHJ P,BLAST↑
;	FATAL(Horrible error!)
DRYRO1:	POP P,RET
	POP P,TAC
	SOSG LOSCNT		;Lost enough times yet?
	EXIT 1,			;  Yes, give up.
	skipn jobddt
	POPJ P,
	skipn debug
	  JRST DRYRO2
	PUSH P,[DRYRO2]
	pop p,jobopc↑
	jrst @jobddt↑

DRYRO2:	IMSKST (P)		;Restore mask
	POP P,(P)
	POPJ P,
SUBTTL Storage
	XLIST			;Don't list the literals!
	LIT
	LIST

PATCH:	 BLOCK 100		;Every big program should have a little

ENDCOD:				;End of pure section

beglok:	0

THISJOB: BLOCK 1		;Own job number
THISNAM: BLOCK 2		;For sending letter to LOGGER
	 SIXBIT/DEBUG?/		;So we get it back from LOGGER!!!
DEBUG:	 BLOCK 1
BEGZER::
CHKSUM:	 BLOCK 1		;Checksum of program
OLDFF:	 BLOCK 1		;Beginning of free space
INTLEV:	 BLOCK 1		;-1 if at interrupt level
NXTTIM:	 BLOCK 1		;Next clock interrupt at this time (tics past midnigth)
LASTRUN: BLOCK 1		;Process last run
SYS.P:	 BLOCK 1		;Top level stack pointer when running process
LAST.P:	 BLOCK 1		;Copy of above just after running process
LASTPC:	 BLOCK 1		;Last user PC
RUNWAIT: BLOCK 1		;Number of processes waiting to be run
USEMAP:	 BLOCK 1		;Map indicating which slots are available (1 if available)
USERS:	 BLOCK 1		;Number of users
IMPMAP:	 BLOCK 1		;Map indicating which slots are in use (1 if in use)
IMPSIZ:	 BLOCK 1		;Size of IMP buffer
TTYSIZ:	 BLOCK 1		;Size of IMP buffer
ALLFUL:	 BLOCK 1		;Set if all our PTY output buffers are full
ALLINP:	 BLOCK 1		;Set if all IMP has process pending to handle it
PTYIWA:	 BLOCK 1		;Some PTY's buffer is full and IMP input is waiting
LOKCNT:	 BLOCK 1		;Number of interlocks
LOKMSK:	 BLOCK 1		;Interrupt mask for interlock kludge
LOSCNT:	 BLOCK 1		;Number of times DRYROT called before giving up

; Statistics
INTCNT:	 BLOCK 1		;Total number of interrupts
INTLOSS: BLOCK 1		;Bad interrupt
CLKCNT:	 BLOCK 1		;Number of clock interrupts
CLTDIF:	 BLOCK 1		;Total number of tics system has been off for interrupts
CLKBAD:	 BLOCK 1		;Number of bad clock interrupts (too early)
IMOCNC:	 BLOCK 1		;Number of times we counted the byte count left
IMOEMC:	 BLOCK 1		;Number of times we checked for an empty bit allocation
IMOEMS:	 BLOCK 1		;Number of times we had to wait for allocation
INPCNT:	 BLOCK 1		;Number of INTPTI interrupts
INPTOC:	 BLOCK 1		;Number of INTPTO interrupts
IMICNT:	 BLOCK 1		;Number of INTINP interrupts

;NXTSOC:	 BLOCK 1		;Next socket for ICP

FREE1K:	 BLOCK 1		;Free 1K block list
PROCFR:	 BLOCK 1		;Free process block list

BEGQUE::			;Beginning of queues (linked lists of processes)
RUNQUE:	 BLOCK QUESIZ		;Requesting to run
PRIQUE:	 BLOCK QUESIZ		;Requesting priority service
CLKQUE:	 BLOCK QUESIZ		;Requesting clock interrupts
IMWQUE:	 BLOCK QUESIZ		;Waiting for IMP input
IMSQUE:	 BLOCK QUESIZ		;Waiting for IMP status change
GRFQUE:	 BLOCK QUESIZ		;Waiting for a process to release graphics channel
LOKQUE:	 BLOCK QUESIZ		;Waiting for interlock
NQUES	←← (.-BEGQUE)/QUESIZ	;Number of queues

INLET:	BLOCK =32		;Incoming mail
OUTLET:	BLOCK =32		;Outgoing mail

;The following two locations must be kept together!!!
OLDMSK:	BLOCK 1			;This is for when we have to do something that
OLDPC:	BLOCK 1			;waits and we were at interrupt level
OLDACS:	BLOCK 20

; User Tables

INHDR:	BLOCK 3*GRFMUL*NUSERS	;Input buffer headers
OUTHDR:	BLOCK 3*GRFMUL*NUSERS	;Output buffer headers
MTBLKS:	BLOCK MTSIZE*(GRFMUL*NUSERS+NSPECU)	;MTAPE blocks
HOSTNA:	BLOCK NUSERS		;Host name
HOSTNU:	BLOCK NUSERS		;Host number
LSOCKT:	BLOCK NUSERS*NUSERS+NSPECU	;Local socket
FSOCKT:	BLOCK GRFMUL*NUSERS	;Foreign socket
BYTSIZ:	BLOCK GRFMUL*NUSERS	;Byte size of connection
BYTUSE:	BLOCK GRFMUL*NUSERS	;Number of bytes used in buffer
BYTLFT:	BLOCK GRFMUL*NUSERS	;Our opinion of how many bytes are left 
FLAGS:	BLOCK NUSERS
IFN DKPRO,<
DKFLAG:	BLOCK GRFMUL*NUSERS+NSPECU	;Losing D. King format!
DKIACT:	BLOCK GRFMUL*NUSERS+NSPECU	;Input channel is empty
DKOACT:	BLOCK NUSERS		;Output is graphics
DKESCF:	BLOCK NUSERS		;Escape seen
DKIHI:	BLOCK NUSERS		;High order bit stored here on input
>;IFN DKPRO
;Special table for FINGER, MAIL, etc.
;Format of each word:	BYTE (12) <PTY number>, (24) SIXBIT/site name/
WHRTAB:	BLOCK NUSERS
	BLOCK 20-NUSERS
	NUSERS
WHOTAB:	BLOCK NUSERS
SPYTAB:	BLOCK NUSERS
WHRLEN=.-WHRTAB
;Following two tables must be kept in order for graphics
PTYNUM:	BLOCK NUSERS		;PTY line number if PTY in use
IFN GRFPRO,<
DPYFLG:	BLOCK NUSERS		;-1 if display output
>
	BLOCK NSPECU
PTOBUF:	BLOCK NUSERS		;Buffers for PTY output
PTIFUL:	BLOCK GRFMUL*NUSERS+NSPECU	;Flag indicating that a process has been scheduled
				;to handle input to PTY
PTOFUL:	BLOCK NUSERS		;Flag indicating NETGRF's PTY ouput buffer full if
				;non-negative
PTBUSY:	BLOCK NUSERS		;Flag indicating PTY's input buffer full
PROMAP:	BLOCK NUSERS		;Bit map indicating which protocols have been sent
IMPST1:	BLOCK 3*(GRFMUL*NUSERS+NSPECU)	;Last known status of IMP channel
CHGFLG:	BLOCK GRFMUL*NUSERS+NSPECU	;IMP status change if non-zero

IFN GRFPRO,<
; Graphics tables
IIFLAG:	BLOCK NUSERS
XMIN:	BLOCK NUSERS		;X minimum (leftmost)
YMIN:	BLOCK NUSERS		;Y minimum (rightmost)
XMUL:	BLOCK NUSERS		;X Multiplier to get user coordinates
YMUL:	BLOCK NUSERS		;Y Multiplier to get user coordinates
XK:	BLOCK NUSERS		;X constant for SNDCOORD (= XMIN+XMUL/2)
YK:	BLOCK NUSERS		;Y constant for SNDCOORD (= YMIN+YMUL/2)
BYTES:	BLOCK NUSERS		;Number bytes to represent user coordinates
ROTS:	BLOCK NUSERS		;Amount to rotate to get first byte
DPYUSE:	BLOCK NUSERS		;Flag indicating display channel in use
IMPLTB:	BLOCK 8*NUSERS		;Bit table indication which commands are implemented
				;by the user program (foreign end)
>;IFN GRFPRO
IFN IMLSW,<
; Imlac mode
IMLACT:	BLOCK NUSERS		;Indicates in Imlac mode
>;IFN IMLSW

; Interlock blocks
$BGLOK::
IMILOK:	BLOCK LOCKSZ		;IMP input
IMOLOK:	BLOCK LOCKSZ		;IMP output
IMCLOK:	BLOCK LOCKSZ		;IMP change interlock
IMMLOK:	BLOCK LOCKSZ		;IMP MTAPE block
PTOLOK:	BLOCK LOCKSZ		;PTY output
.1KLOK:	BLOCK LOCKSZ		;Interlock on 1K free list
PBKLOK:	BLOCK LOCKSZ		;Interlock on process block free list
QUELOK:	BLOCK LOCKSZ		;Interlock on queue change
C17LOK:	BLOCK LOCKSZ		;Interlock on I/O channel 17
DPYLOK:	BLOCK LOCKSZ		;General interlock on graphics
ENDLOK::
VAR
ENDZER←←.-1

;For fast IMPOCH, make pointer into OUTHDR blocks
IMOPPTR:FOR I←0,GRFMUL*NUSERS-1,1 < 3*I+OUTHDR+1↔ >
IMOPCNT:FOR I←0,GRFMUL*NUSERS-1,1 < 3*I+OUTHDR+2↔ >

IMPBLK:	10			;OPEN block for IMP
	SIXBIT/IMP/
	XWD 0,0

CHKNAM:	SIXBIT/GSAV00/
	SIXBIT/TMP/
	0
	0
CRLF:	ASCIZ/
/
SPYTMC:	XWD 'WHO',0
	IOWD 10,SPYBUF
	SIXBIT/100100/
SPYBUF:	BLOCK 10

IFN DEBPRC,<
; Display buffer showing last interrupt
INTDPY:	XWD 400000,.+4
	INDPSZ
	0
	0
	0
	3020B10+765B21+146	;AIVECT -760,765
	ASCID/				I/
NAMLOC:	ASCID/-----/
	ascid/

/]
foodpy:	ascid/AAAAA/
	0
INDPSZ←.-INTDPY-4
>;IFN DEBPRC

IFN CHKSW,<
;The following is called by memory fix routine SYSCHK to print the
;errors.  CHKBUF is compiled by that routine and looks something
;like:
;	XWD =13,2244	;Memory address
;	XWD =8,2244	;Contents
;	XWD =8,PDL+12	;Old contents (saved on stack)
;	XWD 7,CRLF
;	...
CHKMSG:	PUSHJ P,LOGIT
	XWD 7,[ASCIZ/Checksum failure:
/]
	XWD =10,CHKERS
	XWD 7,[ASCIZ/ errors detected.
Address	   Disk	        Core
/]
CHKTAB:	BLOCK =40
	XWD 7,[ASCIZ/.../]
	0
	POPJ P,
CHKERS:	BLOCK 1
CHKIOWD:IOWD ENDCOD-$BGNET,$BGNET
	0
CHKBUF:	BLOCK 200
PARMSG:	PUSHJ P,LOGIT
	XWD 7,[ASCIZ/Parity error: (most recent)
Address	 Prot-Rel       Core
/]
	XWD =13,0
	XWD =14,0
	XWD =14,0
	0
	POPJ P,
>;IFN CHKSW

PDL:	BLOCK 100
PDLIOW:	IOWD .-PDL,PDL

IPDL:	BLOCK 100
IPDLIO:	IOWD .-IPDL,IPDL

LOGPDL:	BLOCK 40
LOGIOW:	IOWD .-LOGPDL,LOGPDL

	END START